/*! \file   janus_audiobridge.c
 * \author Lorenzo Miniero <lorenzo@meetecho.com>
 * \copyright GNU General Public License v3
 * \brief  Janus AudioBridge plugin
 * \details  This is a plugin implementing an audio conference bridge for
 * Janus, specifically mixing Opus streams. This means that it replies
 * by providing in the SDP only support for Opus, and disabling video.
 * Opus encoding and decoding is implemented using libopus (http://opus.codec.org).
 * The plugin provides an API to allow peers to join and leave conference
 * rooms. Peers can then mute/unmute themselves by sending specific messages
 * to the plugin: any way a peer mutes/unmutes, an event is triggered
 * to the other participants, so that it can be rendered in the UI
 * accordingly.
 * 
 * Rooms to make available are listed in the plugin configuration file.
 * A pre-filled configuration file is provided in \c conf/janus.plugin.audiobridge.cfg
 * and includes a demo room for testing.
 * 
 * To add more rooms or modify the existing one, you can use the following
 * syntax:
 * 
 * \verbatim
[<unique room ID>]
description = This is my awesome room
is_private = yes|no (private rooms don't appear when you do a 'list' request)
secret = <optional password needed for manipulating (e.g. destroying) the room>
pin = <optional password needed for joining the room>
sampling_rate = <sampling rate> (e.g., 16000 for wideband mixing)
record = true|false (whether this room should be recorded, default=false)
record_file =	/path/to/recording.wav (where to save the recording)
\endverbatim
 *
 * \section bridgeapi Audio Bridge API
 * 
 * The Audio Bridge API supports several requests, some of which are
 * synchronous and some asynchronous. There are some situations, though,
 * (invalid JSON, invalid request) which will always result in a
 * synchronous error response even for asynchronous requests. 
 * 
 * \c create , \c destroy , \c exists, \c list, \c listparticipants
 * and \c resetdecoder are synchronous requests, which means you'll
 * get a response directly within the context of the transaction.
 * \c create allows you to create a new audio conference bridge
 * dynamically, as an alternative to using the configuration file;
 * \c destroy removes an audio conference bridge and destroys it, kicking
 * all the users out as part of the process; \c exists allows you to
 * check whether a specific audio conference exists; \c list
 * lists all the available rooms, while \c listparticipants lists all
 * the participants of a specific room and their details; finally,
 * \c resetdecoder marks the Opus decoder for the participant as invalid,
 * and forces it to be recreated (which might be needed if the audio
 * for generated by the participant becomes garbled). 
 * 
 * The \c join , \c configure , \c changeroom and \c leave requests
 * instead are all asynchronous, which means you'll get a notification
 * about their success or failure in an event. \c join allows you to
 * join a specific audio conference bridge; \c configure can be used
 * to modify some of the participation settings (e.g., mute/unmute);
 * \c changeroom can be used to leave the current room and move to a
 * different one without having to tear down the PeerConnection and
 * recreate it again (useful for sidebars and "waiting rooms"); finally,
 * \c leave allows you to leave an audio conference bridge for good.
 * 
 * \c create can be used to create a new audio room, and has to be
 * formatted as follows:
 * 
\verbatim
{
	"request" : "create",
	"room" : <unique numeric ID, optional, chosen by plugin if missing>,
	"permanent" : <true|false, whether the room should be saved in the config file, default false>,
	"description" : "<pretty name of the room, optional>",
	"secret" : "<password required to edit/destroy the room, optional>",
	"pin" : "<password required to join the room, optional>",
	"is_private" : <true|false, whether the room should appear in a list request>,
	"sampling" : <sampling rate of the room, optional, 16000 by default>,
	"record" : <true|false, whether to record the room or not, default false>,
	"record_file" : "</path/to/the/recording.wav, optional>",
}
\endverbatim
 *
 * A successful creation procedure will result in a \c created response:
 * 
\verbatim
{
	"audiobridge" : "created",
	"room" : <unique numeric ID>
}
\endverbatim
 *
 * An error instead (and the same applies to all other requests, so this
 * won't be repeated) would provide both an error code and a more verbose
 * description of the cause of the issue:
 * 
\verbatim
{
	"audiobridge" : "event",
	"error_code" : <numeric ID, check Macros below>,
	"error" : "<error description as a string>"
}
\endverbatim
 * 
 * Notice that, in general, all users can create rooms. If you want to
 * limit this functionality, you can configure an admin \c admin_key in
 * the plugin settings. When configured, only "create" requests that
 * include the correct \c admin_key value in an "admin_key" property
 * will succeed, and will be rejected otherwise.
 * 
 * On the other hand, \c destroy can be used to destroy an existing audio
 * room, whether created dynamically or statically, and has to be
 * formatted as follows:
 * 
\verbatim
{
	"request" : "destroy",
	"room" : <unique numeric ID of the room to destroy>,
	"secret" : "<room secret, mandatory if configured>",
	"permanent" : <true|false, whether the room should be also removed from the config file, default false>
}
\endverbatim
 *
 * A successful destruction procedure will result in a \c destroyed response:
 * 
\verbatim
{
	"audiobridge" : "created",
	"room" : <unique numeric ID>
}
\endverbatim
 *
 * You can check whether a room exists using the \c exists request,
 * which has to be formatted as follows:
 * 
\verbatim
{
	"request" : "exists",
	"room" : <unique numeric ID of the room to check>
}
\endverbatim
 *
 * A successful request will result in a \c success response:
 * 
\verbatim
{
	"audiobridge" : "success",
	"room" : <unique numeric ID>,
	"exists" : <true|false>
}
\endverbatim
 * 
 * To get a list of the available rooms (excluded those configured or
 * created as private rooms) you can make use of the \c list request,
 * which has to be formatted as follows:
 * 
\verbatim
{
	"request" : "list"
}
\endverbatim
 *
 * A successful request will produce a list of rooms in a \c success response:
 * 
\verbatim
{
	"audiobridge" : "success",
	"rooms" : [		// Array of room objects
		{	// Room #1
			"room" : <unique numeric ID>,
			"description" : "<Name of the room>",
			"sampling_rate" : <sampling rate of the mixer>,
			"record" : <true|false, whether the room is being recorded>,
			"num_participants" : <count of the participants>
		},
		// Other rooms
	]
}
\endverbatim
 *
 * To get a list of the participants in a specific room, instead, you
 * can make use of the \c listparticipants request, which has to be
 * formatted as follows:
 * 
\verbatim
{
	"request" : "listparticipants",
	"room" : <unique numeric ID of the room>
}
\endverbatim
 *
 * A successful request will produce a list of participants in a
 * \c participants response:
 * 
\verbatim
{
	"audiobridge" : "participants",
	"room" : <unique numeric ID of the room>,
	"participants" : [		// Array of participant objects
		{	// Participant #1
			"id" : <unique numeric ID of the participant>,
			"display" : "<display name of the participant, if any; optional>",
			"muted" : <true|false, whether user is muted or not>
		},
		// Other participants
	]
}
\endverbatim
 * 
 * To mark the Opus decoder context for the current participant as
 * invalid and force it to be recreated, use the \c resetdecoder request:
 * 
\verbatim
{
	"request" : "resetdecoder"
}
\endverbatim
 *
 * A successful request will produce a \c success response:
 * 
\verbatim
{
	"audiobridge" : "success"
}
\endverbatim
 * 
 * That completes the list of synchronous requests you can send to the
 * AudioBridge plugin. As anticipated, though, there are also several
 * asynchronous requests you can send, specifically those related to
 * joining and updating one's presence as a participant in an audio room.
 * 
 * The way you'd interact with the plugin is usually as follows:
 * 
 * -# you use a \c join request to join an audio room, and wait for the
 * \c joined event; this event will also include a list of the other
 * participants, if any;
 * -# you send a \c configure request attached to an audio-only JSEP offer
 * to start configuring your participation in the room (e.g., join unmuted
 * or muted), and wait for a \c configured event, which will be attached
 * to a JSEP answer by the plugin to complete the setup of the WebRTC
 * PeerConnection;
 * -# you send other \c configure requests (without any JSEP-related
 * attachment) to mute/unmute yourself during the audio conference;
 * -# you intercept events originated by the plugin (\c joined , \c leaving )
 * to notify you about users joining/leaving/muting/unmuting;
 * -# you eventually send a \c leave request to leave a room; if you leave the
 * PeerConnection instance intact, you can subsequently join a different
 * room without requiring a new negotiation (and so just use a \c join + JSEP-less \c configure to join).
 * 
 * Notice that there's also a \c changeroom request available: you can use
 * this request to immediately leave the room you're in and join a different
 * one, without requiring you to do a \c leave + \c join + \c configure
 * round. Of course remember not to pass any JSEP-related payload when
 * doing a \c changeroom as the same pre-existing PeerConnection will be
 * re-used for the purpose.
 * 
 * About the syntax of all the above mentioned requests, \c join has
 * to be formatted as follows:
 * 
\verbatim
{
	"request" : "join",
	"room" : <numeric ID of the room to join>,
	"id" : <unique ID to assign to the participant; optional, assigned by the plugin if missing>,
	"pin" : "<password required to join the room, if any; optional>",
	"display" : "<display name to have in the room; optional>",
	"muted" : <true|false, whether to start unmuted or muted>,
	"quality" : <0-10, Opus-related complexity to use, lower is higher quality; optional, default is 4>
}
\endverbatim
 *
 * A successful request will produce a \c joined event:
 * 
\verbatim
{
	"audiobridge" : "joined",
	"room" : <numeric ID of the room>,
	"id" : <unique ID assigned to the participant>,
	"display" : "<display name of the new participant>",
	"participants" : [
		// Array of existing participants in the room
	]
}
\endverbatim
 * 
 * The other participants in the room will be notified about the new
 * participant by means of a different \c joined event, which will only
 * include the \c room and the new participant as the only object in
 * a \c participants array.
 *
 * At this point, the media-related settings of the participant can be
 * modified by means of a \c configure request. The \c configure request
 * has to be formatted as follows:
 *
\verbatim
{
	"request" : "configure",
	"muted" : <true|false, whether to unmute or mute>,
	"quality" : <0-10, Opus-related complexity to use, lower is higher quality; optional, default is 4>,
	"record": <true|false, whether to record this user's contribution to a .mjr file (mixer not involved),
	"filename": "<basename of the file to record to, -audio.mjr will be added by the plugin>"
}
\endverbatim
 *
 * \c muted instructs the plugin to mute or unmute the participant;
 * \c quality changes the complexity of the Opus encoder for the
 * participant; \c record can be used to record this participant's contribution
 * to a Janus .mjr file, and \c filename to provide a basename for the path to
 * save the file to (notice that this is different from the recording of a whole
 * room: this feature only records the packets this user is sending, and is not
 * related to the mixer stuff). A successful request will result in a \c ok event:
 * 
\verbatim
{
	"audiobridge" : "event",
	"room" : <numeric ID of the room>,
	"result" : "ok"
}
\endverbatim
 *
 * In case the \c muted property was modified, the other participants in
 * the room will be notified about this by means of a \c event notification,
 * which will only include the \c room and the updated participant as the
 * only object in a \c participants array.
 *
 * As anticipated, you can leave an audio room using the \c leave request,
 * which has to be formatted as follows:
 * 
\verbatim
{
	"request" : "leave"
}
\endverbatim
 * 
 * All the participants will receive an \c event notification with the
 * ID of the participant who just left:
 * 
\verbatim
{
	"audiobridge" : "event",
	"room" : <numeric ID of the room>,
	"leaving" : <numeric ID of the participant who left>
}
\endverbatim
 *
 * For what concerns the \c changeroom request, instead, it's pretty much
 * the same as a \c join request and as such has to be formatted as follows:
 * 
\verbatim
{
	"request" : "changeroom",
	"room" : <numeric ID of the room to move to>,
	"id" : <unique ID to assign to the participant; optional, assigned by the plugin if missing>,
	"display" : "<display name to have in the room; optional>",
	"muted" : <true|false, whether to start unmuted or muted>,
	"quality" : <0-10, Opus-related complexity to use, lower is higher quality; optional, default is 4>
}
\endverbatim
 * 
 * Such a request will trigger all the above-described leaving/joined
 * events to the other participants, as it is indeed wrapping a \c leave
 * followed by a \c join and as such the other participants in both rooms
 * need to be updated accordingly. The participant who switched room
 * instead will be sent a \c roomchanged event which is pretty similar
 * to what \c joined looks like:
 * 
 * A successful request will produce a \c joined event:
 * 
\verbatim
{
	"audiobridge" : "roomchanged",
	"room" : <numeric ID of the new room>,
	"id" : <unique ID assigned to the participant in the new room>,
	"display" : "<display name of the new participant>",
	"participants" : [
		// Array of existing participants in the new room
	]
}
\endverbatim
 *  
 * \ingroup plugins
 * \ref plugins
 */

#include "plugin.h"

#include <jansson.h>
#include <opus/opus.h>
#include <sys/time.h>

#include "../debug.h"
#include "../apierror.h"
#include "../config.h"
#include "../mutex.h"
#include "../rtp.h"
#include "../rtcp.h"
#include "../record.h"
#include "../utils.h"


/* Plugin information */
#define JANUS_AUDIOBRIDGE_VERSION			9
#define JANUS_AUDIOBRIDGE_VERSION_STRING	"0.0.9"
#define JANUS_AUDIOBRIDGE_DESCRIPTION		"This is a plugin implementing an audio conference bridge for Janus, mixing Opus streams."
#define JANUS_AUDIOBRIDGE_NAME				"JANUS AudioBridge plugin"
#define JANUS_AUDIOBRIDGE_AUTHOR			"Meetecho s.r.l."
#define JANUS_AUDIOBRIDGE_PACKAGE			"janus.plugin.audiobridge"

/* Plugin methods */
janus_plugin *create(void);
int janus_audiobridge_init(janus_callbacks *callback, const char *config_path);
void janus_audiobridge_destroy(void);
int janus_audiobridge_get_api_compatibility(void);
int janus_audiobridge_get_version(void);
const char *janus_audiobridge_get_version_string(void);
const char *janus_audiobridge_get_description(void);
const char *janus_audiobridge_get_name(void);
const char *janus_audiobridge_get_author(void);
const char *janus_audiobridge_get_package(void);
void janus_audiobridge_create_session(janus_plugin_session *handle, int *error);
struct janus_plugin_result *janus_audiobridge_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep);
void janus_audiobridge_setup_media(janus_plugin_session *handle);
void janus_audiobridge_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len);
void janus_audiobridge_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len);
void janus_audiobridge_hangup_media(janus_plugin_session *handle);
void janus_audiobridge_destroy_session(janus_plugin_session *handle, int *error);
json_t *janus_audiobridge_query_session(janus_plugin_session *handle);

/* Plugin setup */
static janus_plugin janus_audiobridge_plugin =
	JANUS_PLUGIN_INIT (
		.init = janus_audiobridge_init,
		.destroy = janus_audiobridge_destroy,

		.get_api_compatibility = janus_audiobridge_get_api_compatibility,
		.get_version = janus_audiobridge_get_version,
		.get_version_string = janus_audiobridge_get_version_string,
		.get_description = janus_audiobridge_get_description,
		.get_name = janus_audiobridge_get_name,
		.get_author = janus_audiobridge_get_author,
		.get_package = janus_audiobridge_get_package,
		
		.create_session = janus_audiobridge_create_session,
		.handle_message = janus_audiobridge_handle_message,
		.setup_media = janus_audiobridge_setup_media,
		.incoming_rtp = janus_audiobridge_incoming_rtp,
		.incoming_rtcp = janus_audiobridge_incoming_rtcp,
		.hangup_media = janus_audiobridge_hangup_media,
		.destroy_session = janus_audiobridge_destroy_session,
		.query_session = janus_audiobridge_query_session,
	);

/* Plugin creator */
janus_plugin *create(void) {
	JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_AUDIOBRIDGE_NAME);
	return &janus_audiobridge_plugin;
}

/* Parameter validation */
static struct janus_json_parameter request_parameters[] = {
	{"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
};
static struct janus_json_parameter adminkey_parameters[] = {
	{"admin_key", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
};
static struct janus_json_parameter create_parameters[] = {
	{"description", JSON_STRING, 0},
	{"secret", JSON_STRING, 0},
	{"pin", JSON_STRING, 0},
	{"is_private", JANUS_JSON_BOOL, 0},
	{"sampling", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"record", JANUS_JSON_BOOL, 0},
	{"record_file", JSON_STRING, 0},
	{"permanent", JANUS_JSON_BOOL, 0},
	{"room", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}
};
static struct janus_json_parameter destroy_parameters[] = {
	{"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
	{"permanent", JANUS_JSON_BOOL, 0}
};
static struct janus_json_parameter room_parameters[] = {
	{"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE}
};
static struct janus_json_parameter join_parameters[] = {
	{"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
	{"display", JSON_STRING, 0},
	{"muted", JANUS_JSON_BOOL, 0},
	{"quality", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"id", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}
};
static struct janus_json_parameter configure_parameters[] = {
	{"muted", JANUS_JSON_BOOL, 0},
	{"quality", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
	{"record", JANUS_JSON_BOOL, 0},
	{"filename", JSON_STRING, 0}
};

/* Static configuration instance */
static janus_config *config = NULL;
static const char *config_folder = NULL;
static janus_mutex config_mutex;

/* Useful stuff */
static volatile gint initialized = 0, stopping = 0;
static janus_callbacks *gateway = NULL;
static GThread *handler_thread;
static GThread *watchdog;
static void *janus_audiobridge_handler(void *data);
static void janus_audiobridge_relay_rtp_packet(gpointer data, gpointer user_data);
static void *janus_audiobridge_mixer_thread(void *data);
static void *janus_audiobridge_participant_thread(void *data);

typedef struct janus_audiobridge_message {
	janus_plugin_session *handle;
	char *transaction;
	json_t *message;
	json_t *jsep;
} janus_audiobridge_message;
static GAsyncQueue *messages = NULL;
static janus_audiobridge_message exit_message;

static void janus_audiobridge_message_free(janus_audiobridge_message *msg) {
	if(!msg || msg == &exit_message)
		return;

	msg->handle = NULL;

	g_free(msg->transaction);
	msg->transaction = NULL;
	if(msg->message)
		json_decref(msg->message);
	msg->message = NULL;
	if(msg->jsep)
		json_decref(msg->jsep);
	msg->jsep = NULL;

	g_free(msg);
}


typedef struct janus_audiobridge_room {
	guint64 room_id;			/* Unique room ID */
	gchar *room_name;			/* Room description */
	gchar *room_secret;			/* Secret needed to manipulate (e.g., destroy) this room */
	gchar *room_pin;			/* Password needed to join this room, if any */
	gboolean is_private;			/* Whether this room is 'private' (as in hidden) or not */
	uint32_t sampling_rate;		/* Sampling rate of the mix (e.g., 16000 for wideband; can be 8, 12, 16, 24 or 48kHz) */
	gboolean record;			/* Whether this room has to be recorded or not */
	gchar *record_file;			/* Path of the recording file */
	FILE *recording;			/* File to record the room into */
	gint64 record_lastupdate;	/* Time when we last updated the wav header */
	gboolean destroy;			/* Value to flag the room for destruction */
	GHashTable *participants;	/* Map of participants */
	GThread *thread;			/* Mixer thread for this room */
	gint64 destroyed;			/* When this room has been destroyed */
	janus_mutex mutex;			/* Mutex to lock this room instance */
} janus_audiobridge_room;
static GHashTable *rooms;
static janus_mutex rooms_mutex;
static GList *old_rooms;
static char *admin_key = NULL;

typedef struct janus_audiobridge_session {
	janus_plugin_session *handle;
	gpointer participant;
	gboolean started;
	gboolean stopping;
	volatile gint hangingup;
	gint64 destroyed;	/* Time at which this session was marked as destroyed */
} janus_audiobridge_session;
static GHashTable *sessions;
static GList *old_sessions;
static janus_mutex sessions_mutex;

typedef struct janus_audiobridge_rtp_context {
	/* Needed to fix seq and ts in case of publisher switching */
	uint32_t a_last_ssrc, a_last_ts, a_base_ts, a_base_ts_prev;
	uint16_t a_last_seq, a_base_seq, a_base_seq_prev;
} janus_audiobridge_rtp_context;

typedef struct janus_audiobridge_participant {
	janus_audiobridge_session *session;
	janus_audiobridge_room *room;	/* Room */
	guint64 user_id;		/* Unique ID in the room */
	gchar *display;			/* Display name (just for fun) */
	gboolean prebuffering;	/* Whether this participant needs pre-buffering of a few packets (just joined) */
	gboolean active;		/* Whether this participant can receive media at all */
	gboolean working;		/* Whether this participant is currently encoding/decoding */
	gboolean muted;			/* Whether this participant is muted */
	int opus_complexity;	/* Complexity to use in the encoder (by default, DEFAULT_COMPLEXITY) */
	/* RTP stuff */
	GList *inbuf;			/* Incoming audio from this participant, as an ordered list of packets */
	GAsyncQueue *outbuf;	/* Mixed audio for this participant */
	janus_mutex qmutex;		/* Incoming queue mutex */
	int opus_pt;			/* Opus payload type */
	janus_audiobridge_rtp_context context;	/* Needed in case the participant changes room */
	/* Opus stuff */
	OpusEncoder *encoder;		/* Opus encoder instance */
	OpusDecoder *decoder;		/* Opus decoder instance */
	gboolean reset;				/* Whether or not the Opus context must be reset, without re-joining the room */
	GThread *thread;			/* Encoding thread for this participant */
	janus_recorder *arc;		/* The Janus recorder instance for this user's audio, if enabled */
	janus_mutex rec_mutex;		/* Mutex to protect the recorder from race conditions */
	gint64 destroyed;			/* When this participant has been destroyed */
} janus_audiobridge_participant;

/* Packets we get from gstreamer and relay */
typedef struct janus_audiobridge_rtp_relay_packet {
	rtp_header *data;
	gint length;
	uint32_t ssrc;
	uint32_t timestamp;
	uint16_t seq_number;
} janus_audiobridge_rtp_relay_packet;

/* Helper to sort incoming RTP packets by sequence numbers */
static gint janus_audiobridge_rtp_sort(gconstpointer a, gconstpointer b) {
	janus_audiobridge_rtp_relay_packet *pkt1 = (janus_audiobridge_rtp_relay_packet *)a;
	janus_audiobridge_rtp_relay_packet *pkt2 = (janus_audiobridge_rtp_relay_packet *)b;
	if(pkt1->seq_number < 100 && pkt2->seq_number > 65000) {
		/* Sequence number was probably reset, pkt2 is older */
		return 1;
	} else if(pkt2->seq_number < 100 && pkt1->seq_number > 65000) {
		/* Sequence number was probably reset, pkt1 is older */
		return -1;
	}
	/* Simply compare timestamps */
	if(pkt1->seq_number < pkt2->seq_number)
		return -1;
	else if(pkt1->seq_number > pkt2->seq_number)
		return 1;
	return 0;
}

/* SDP offer/answer template */
#define sdp_template \
		"v=0\r\n" \
		"o=- %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n"	/* We need current time here */ \
		"s=%s\r\n"							/* Audio bridge name */ \
		"t=0 0\r\n" \
		"m=audio 1 RTP/SAVPF %d\r\n"		/* Opus payload type */ \
		"c=IN IP4 1.1.1.1\r\n" \
		"a=rtpmap:%d opus/48000/2\r\n"		/* Opus payload type */ \
		"a=fmtp:%d maxplaybackrate=%"SCNu32"; stereo=0; sprop-stereo=0; useinbandfec=0\r\n" \
											/* Opus payload type and room sampling rate */

/* Helper struct to generate and parse WAVE headers */
typedef struct wav_header {
	char riff[4];
	uint32_t len;
	char wave[4];
	char fmt[4];
	uint32_t formatsize;
	uint16_t format;
	uint16_t channels;
	uint32_t samplerate;
	uint32_t avgbyterate;
	uint16_t samplebytes;
	uint16_t channelbits;
	char data[4];
	uint32_t blocksize;
} wav_header;


/* Mixer settings */
#define DEFAULT_PREBUFFERING	6


/* Opus settings */		
#define	BUFFER_SAMPLES	8000
#define	OPUS_SAMPLES	160
#define USE_FEC			0
#define DEFAULT_COMPLEXITY	4


/* Error codes */
#define JANUS_AUDIOBRIDGE_ERROR_UNKNOWN_ERROR	499
#define JANUS_AUDIOBRIDGE_ERROR_NO_MESSAGE		480
#define JANUS_AUDIOBRIDGE_ERROR_INVALID_JSON	481
#define JANUS_AUDIOBRIDGE_ERROR_INVALID_REQUEST	482
#define JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT	483
#define JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT	484
#define JANUS_AUDIOBRIDGE_ERROR_NO_SUCH_ROOM	485
#define JANUS_AUDIOBRIDGE_ERROR_ROOM_EXISTS		486
#define JANUS_AUDIOBRIDGE_ERROR_NOT_JOINED		487
#define JANUS_AUDIOBRIDGE_ERROR_LIBOPUS_ERROR	488
#define JANUS_AUDIOBRIDGE_ERROR_UNAUTHORIZED	489
#define JANUS_AUDIOBRIDGE_ERROR_ID_EXISTS		490
#define JANUS_AUDIOBRIDGE_ERROR_ALREADY_JOINED	491


/* AudioBridge watchdog/garbage collector (sort of) */
void *janus_audiobridge_watchdog(void *data);
void *janus_audiobridge_watchdog(void *data) {
	JANUS_LOG(LOG_INFO, "AudioBridge watchdog started\n");
	gint64 now = 0;
	while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
		janus_mutex_lock(&sessions_mutex);
		/* Iterate on all the sessions */
		now = janus_get_monotonic_time();
		if(old_sessions != NULL) {
			GList *sl = old_sessions;
			JANUS_LOG(LOG_HUGE, "Checking %d old AudioBridge sessions...\n", g_list_length(old_sessions));
			while(sl) {
				janus_audiobridge_session *session = (janus_audiobridge_session *)sl->data;
				if(!session) {
					sl = sl->next;
					continue;
				}
				if(now-session->destroyed >= 5*G_USEC_PER_SEC) {
					/* We're lazy and actually get rid of the stuff only after a few seconds */
					JANUS_LOG(LOG_VERB, "Freeing old AudioBridge session\n");
					GList *rm = sl->next;
					old_sessions = g_list_delete_link(old_sessions, sl);
					sl = rm;
					session->handle = NULL;
					g_free(session);
					session = NULL;
					continue;
				}
				sl = sl->next;
			}
		}
		janus_mutex_unlock(&sessions_mutex);
		janus_mutex_lock(&rooms_mutex);
		if(old_rooms != NULL) {
			GList *rl = old_rooms;
			now = janus_get_monotonic_time();
			while(rl) {
				janus_audiobridge_room *audiobridge = (janus_audiobridge_room*)rl->data;
				if(!initialized || stopping){
					break;
				}
				if(!audiobridge) {
					rl = rl->next;
					continue;
				}
				if(now - audiobridge->destroyed >= 5*G_USEC_PER_SEC) {
					/* Free resources */
					JANUS_LOG(LOG_VERB, "Freeing old AudioBridge room %"SCNu64"\n", audiobridge->room_id);
					g_free(audiobridge->room_name);
					g_free(audiobridge->room_secret);
					g_free(audiobridge->room_pin);
					g_free(audiobridge->record_file);
					g_hash_table_destroy(audiobridge->participants);
					g_free(audiobridge);
					/* Move on */
					GList *rm = rl->next;
					old_rooms = g_list_delete_link(old_rooms, rl);
					rl = rm;
					continue;
				}
				rl = rl->next;
			}
		}
		janus_mutex_unlock(&rooms_mutex);
		g_usleep(500000);
	}
	JANUS_LOG(LOG_INFO, "AudioBridge watchdog stopped\n");
	return NULL;
}


/* Plugin implementation */
int janus_audiobridge_init(janus_callbacks *callback, const char *config_path) {
	if(g_atomic_int_get(&stopping)) {
		/* Still stopping from before */
		return -1;
	}
	if(callback == NULL || config_path == NULL) {
		/* Invalid arguments */
		return -1;
	}

	/* Read configuration */
	char filename[255];
	g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_AUDIOBRIDGE_PACKAGE);
	JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
	config = janus_config_parse(filename);
	config_folder = config_path;
	if(config != NULL)
		janus_config_print(config);
	janus_mutex_init(&config_mutex);
	
	rooms = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, NULL);
	janus_mutex_init(&rooms_mutex);
	sessions = g_hash_table_new(NULL, NULL);
	janus_mutex_init(&sessions_mutex);
	messages = g_async_queue_new_full((GDestroyNotify) janus_audiobridge_message_free);
	/* This is the callback we'll need to invoke to contact the gateway */
	gateway = callback;

	/* Parse configuration to populate the rooms list */
	if(config != NULL) {
		/* Any admin key to limit who can "create"? */
		janus_config_item *key = janus_config_get_item_drilldown(config, "general", "admin_key");
		if(key != NULL && key->value != NULL)
			admin_key = g_strdup(key->value);
		/* Iterate on all rooms */
		GList *cl = janus_config_get_categories(config);
		while(cl != NULL) {
			janus_config_category *cat = (janus_config_category *)cl->data;
			if(cat->name == NULL || !strcasecmp(cat->name, "general")) {
				cl = cl->next;
				continue;
			}
			JANUS_LOG(LOG_VERB, "Adding audio room '%s'\n", cat->name);
			janus_config_item *desc = janus_config_get_item(cat, "description");
			janus_config_item *priv = janus_config_get_item(cat, "is_private");
			janus_config_item *sampling = janus_config_get_item(cat, "sampling_rate");
			janus_config_item *secret = janus_config_get_item(cat, "secret");
			janus_config_item *pin = janus_config_get_item(cat, "pin");
			janus_config_item *record = janus_config_get_item(cat, "record");
			janus_config_item *recfile = janus_config_get_item(cat, "record_file");
			if(sampling == NULL || sampling->value == NULL) {
				JANUS_LOG(LOG_ERR, "Can't add the audio room, missing mandatory information...\n");
				cl = cl->next;
				continue;
			}
			/* Create the audio bridge room */
			janus_audiobridge_room *audiobridge = g_malloc0(sizeof(janus_audiobridge_room));
			audiobridge->room_id = atol(cat->name);
			char *description = NULL;
			if(desc != NULL && desc->value != NULL && strlen(desc->value) > 0)
				description = g_strdup(desc->value);
			else
				description = g_strdup(cat->name);
			audiobridge->room_name = description;
			audiobridge->is_private = priv && priv->value && janus_is_true(priv->value);
			audiobridge->sampling_rate = atol(sampling->value);
			switch(audiobridge->sampling_rate) {
				case 8000:
				case 12000:
				case 16000:
				case 24000:
				case 48000:
					JANUS_LOG(LOG_VERB, "Sampling rate for mixing: %"SCNu32"\n", audiobridge->sampling_rate);
					break;
				default:
					JANUS_LOG(LOG_ERR, "Unsupported sampling rate %"SCNu32"...\n", audiobridge->sampling_rate);
					cl = cl->next;
					continue;
			}
			if(secret != NULL && secret->value != NULL) {
				audiobridge->room_secret = g_strdup(secret->value);
			}
			if(pin != NULL && pin->value != NULL) {
				audiobridge->room_pin = g_strdup(pin->value);
			}
			audiobridge->record = FALSE;
			if(record && record->value && janus_is_true(record->value))
				audiobridge->record = TRUE;
			if(recfile && recfile->value)
				audiobridge->record_file = g_strdup(recfile->value);
			audiobridge->recording = NULL;
			audiobridge->destroy = 0;
			audiobridge->participants = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, NULL);
			audiobridge->destroyed = 0;
			janus_mutex_init(&audiobridge->mutex);
			JANUS_LOG(LOG_VERB, "Created audiobridge: %"SCNu64" (%s, %s, secret: %s, pin: %s)\n",
				audiobridge->room_id, audiobridge->room_name,
				audiobridge->is_private ? "private" : "public",
				audiobridge->room_secret ? audiobridge->room_secret : "no secret",
				audiobridge->room_pin ? audiobridge->room_pin : "no pin");
			/* We need a thread for the mix */
			GError *error = NULL;
			char tname[16];
			g_snprintf(tname, sizeof(tname), "mixer %"SCNu64, audiobridge->room_id);
			audiobridge->thread = g_thread_try_new(tname, &janus_audiobridge_mixer_thread, audiobridge, &error);
			if(error != NULL) {
				/* FIXME We should clear some resources... */
				JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the mixer thread...\n", error->code, error->message ? error->message : "??");
			} else {
				janus_mutex_lock(&rooms_mutex);
				g_hash_table_insert(rooms, janus_uint64_dup(audiobridge->room_id), audiobridge);
				janus_mutex_unlock(&rooms_mutex);
			}
			cl = cl->next;
		}
		/* Done: we keep the configuration file open in case we get a "create" or "destroy" with permanent=true */
	}

	/* Show available rooms */
	janus_mutex_lock(&rooms_mutex);
	GHashTableIter iter;
	gpointer value;
	g_hash_table_iter_init(&iter, rooms);
	while (g_hash_table_iter_next(&iter, NULL, &value)) {
		janus_audiobridge_room *ar = value;
		JANUS_LOG(LOG_VERB, "  ::: [%"SCNu64"][%s] %"SCNu32" (%s be recorded)\n",
			ar->room_id, ar->room_name, ar->sampling_rate, ar->record ? "will" : "will NOT");
	}
	janus_mutex_unlock(&rooms_mutex);

	g_atomic_int_set(&initialized, 1);

	GError *error = NULL;
	/* Start the sessions watchdog */
	watchdog = g_thread_try_new("audiobridge watchdog", &janus_audiobridge_watchdog, NULL, &error);
	if(error != NULL) {
		g_atomic_int_set(&initialized, 0);
		JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the AudioBridge watchdog thread...\n", error->code, error->message ? error->message : "??");
		janus_config_destroy(config);
		return -1;
	}
	/* Launch the thread that will handle incoming messages */
	handler_thread = g_thread_try_new("audiobridge handler", janus_audiobridge_handler, NULL, &error);
	if(error != NULL) {
		g_atomic_int_set(&initialized, 0);
		JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the AudioBridge handler thread...\n", error->code, error->message ? error->message : "??");
		janus_config_destroy(config);
		return -1;
	}
	JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_AUDIOBRIDGE_NAME);
	return 0;
}

void janus_audiobridge_destroy(void) {
	if(!g_atomic_int_get(&initialized))
		return;
	g_atomic_int_set(&stopping, 1);

	g_async_queue_push(messages, &exit_message);
	if(handler_thread != NULL) {
		g_thread_join(handler_thread);
		handler_thread = NULL;
	}
	if(watchdog != NULL) {
		g_thread_join(watchdog);
		watchdog = NULL;
	}
	/* FIXME We should destroy the sessions cleanly */
	janus_mutex_lock(&sessions_mutex);
	g_hash_table_destroy(sessions);
	janus_mutex_unlock(&sessions_mutex);
	janus_mutex_lock(&rooms_mutex);
	g_hash_table_destroy(rooms);
	janus_mutex_unlock(&rooms_mutex);
	g_async_queue_unref(messages);
	messages = NULL;
	sessions = NULL;

	janus_config_destroy(config);
	g_free(admin_key);

	g_atomic_int_set(&initialized, 0);
	g_atomic_int_set(&stopping, 0);
	JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_AUDIOBRIDGE_NAME);
}

int janus_audiobridge_get_api_compatibility(void) {
	/* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
	return JANUS_PLUGIN_API_VERSION;
}

int janus_audiobridge_get_version(void) {
	return JANUS_AUDIOBRIDGE_VERSION;
}

const char *janus_audiobridge_get_version_string(void) {
	return JANUS_AUDIOBRIDGE_VERSION_STRING;
}

const char *janus_audiobridge_get_description(void) {
	return JANUS_AUDIOBRIDGE_DESCRIPTION;
}

const char *janus_audiobridge_get_name(void) {
	return JANUS_AUDIOBRIDGE_NAME;
}

const char *janus_audiobridge_get_author(void) {
	return JANUS_AUDIOBRIDGE_AUTHOR;
}

const char *janus_audiobridge_get_package(void) {
	return JANUS_AUDIOBRIDGE_PACKAGE;
}

void janus_audiobridge_create_session(janus_plugin_session *handle, int *error) {
	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
		*error = -1;
		return;
	}	
	janus_audiobridge_session *session = (janus_audiobridge_session *)g_malloc0(sizeof(janus_audiobridge_session));
	session->handle = handle;
	session->started = FALSE;
	session->stopping = FALSE;
	session->destroyed = 0;
	g_atomic_int_set(&session->hangingup, 0);
	handle->plugin_handle = session;
	janus_mutex_lock(&sessions_mutex);
	g_hash_table_insert(sessions, handle, session);
	janus_mutex_unlock(&sessions_mutex);

	return;
}

void janus_audiobridge_destroy_session(janus_plugin_session *handle, int *error) {
	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
		*error = -1;
		return;
	}	
	janus_audiobridge_session *session = (janus_audiobridge_session *)handle->plugin_handle; 
	if(!session) {
		JANUS_LOG(LOG_ERR, "No AudioBridge session associated with this handle...\n");
		*error = -2;
		return;
	}
	JANUS_LOG(LOG_VERB, "Removing AudioBridge session...\n");
	janus_mutex_lock(&sessions_mutex);
	if(!session->destroyed) {
		g_hash_table_remove(sessions, handle);
		janus_audiobridge_hangup_media(handle);
		session->destroyed = janus_get_monotonic_time();
		/* Cleaning up and removing the session is done in a lazy way */
		old_sessions = g_list_append(old_sessions, session);
	}
	janus_mutex_unlock(&sessions_mutex);

	return;
}

json_t *janus_audiobridge_query_session(janus_plugin_session *handle) {
	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
		return NULL;
	}	
	janus_audiobridge_session *session = (janus_audiobridge_session *)handle->plugin_handle;
	if(!session) {
		JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
		return NULL;
	}
	/* Show the participant/room info, if any */
	json_t *info = json_object();
	janus_audiobridge_participant *participant = (janus_audiobridge_participant *)session->participant;
	json_object_set_new(info, "state", json_string(participant && participant->room ? "inroom" : "idle"));
	if(participant) {
		janus_mutex_lock(&rooms_mutex);
		janus_audiobridge_room *room = participant->room;
		if(room != NULL)
			json_object_set_new(info, "room", json_integer(room->room_id));
		janus_mutex_unlock(&rooms_mutex);
		json_object_set_new(info, "id", json_integer(participant->user_id));
		if(participant->display)
			json_object_set_new(info, "display", json_string(participant->display));
		json_object_set_new(info, "muted", participant->muted ? json_true() : json_false());
		json_object_set_new(info, "active", participant->active ? json_true() : json_false());
		json_object_set_new(info, "pre-buffering", participant->prebuffering ? json_true() : json_false());
		if(participant->inbuf) {
			janus_mutex_lock(&participant->qmutex);
			json_object_set_new(info, "queue-in", json_integer(g_list_length(participant->inbuf)));
			janus_mutex_unlock(&participant->qmutex);
		}
		if(participant->outbuf)
			json_object_set_new(info, "queue-out", json_integer(g_async_queue_length(participant->outbuf)));
		if(participant->arc && participant->arc->filename)
			json_object_set_new(info, "audio-recording", json_string(participant->arc->filename));
	}
	json_object_set_new(info, "started", session->started ? json_true() : json_false());
	json_object_set_new(info, "destroyed", json_integer(session->destroyed));
	return info;
}

struct janus_plugin_result *janus_audiobridge_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep) {
	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
		return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized", NULL);

	/* Pre-parse the message */
	int error_code = 0;
	char error_cause[512];
	json_t *root = message;
	json_t *response = NULL;
	
	if(message == NULL) {
		JANUS_LOG(LOG_ERR, "No message??\n");
		error_code = JANUS_AUDIOBRIDGE_ERROR_NO_MESSAGE;
		g_snprintf(error_cause, 512, "%s", "No message??");
		goto plugin_response;
	}

	janus_audiobridge_session *session = (janus_audiobridge_session *)handle->plugin_handle;	
	if(!session) {
		JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
		error_code = JANUS_AUDIOBRIDGE_ERROR_UNKNOWN_ERROR;
		g_snprintf(error_cause, 512, "%s", "session associated with this handle...");
		goto plugin_response;
	}
	if(session->destroyed) {
		JANUS_LOG(LOG_ERR, "Session has already been marked as destroyed...\n");
		error_code = JANUS_AUDIOBRIDGE_ERROR_UNKNOWN_ERROR;
		g_snprintf(error_cause, 512, "%s", "Session has already been marked as destroyed...");
		goto plugin_response;
	}
	if(!json_is_object(root)) {
		JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
		error_code = JANUS_AUDIOBRIDGE_ERROR_INVALID_JSON;
		g_snprintf(error_cause, 512, "JSON error: not an object");
		goto plugin_response;
	}
	/* Get the request first */
	JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
		error_code, error_cause, TRUE,
		JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT);
	if(error_code != 0)
		goto plugin_response;
	json_t *request = json_object_get(root, "request");
	/* Some requests ('create', 'destroy', 'exists', 'list') can be handled synchronously */
	const char *request_text = json_string_value(request);
	if(!strcasecmp(request_text, "create")) {
		/* Create a new audiobridge */
		JANUS_LOG(LOG_VERB, "Creating a new audiobridge\n");
		JANUS_VALIDATE_JSON_OBJECT(root, create_parameters,
			error_code, error_cause, TRUE,
			JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT);
		if(error_code != 0)
			goto plugin_response;
		if(admin_key != NULL) {
			/* An admin key was specified: make sure it was provided, and that it's valid */
			JANUS_VALIDATE_JSON_OBJECT(root, adminkey_parameters,
				error_code, error_cause, TRUE,
				JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT);
			if(error_code != 0)
				goto plugin_response;
			JANUS_CHECK_SECRET(admin_key, root, "admin_key", error_code, error_cause,
				JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_UNAUTHORIZED);
			if(error_code != 0)
				goto plugin_response;
		}
		json_t *desc = json_object_get(root, "description");
		json_t *secret = json_object_get(root, "secret");
		json_t *pin = json_object_get(root, "pin");
		json_t *is_private = json_object_get(root, "is_private");
		json_t *sampling = json_object_get(root, "sampling");
		json_t *record = json_object_get(root, "record");
		json_t *recfile = json_object_get(root, "record_file");
		json_t *permanent = json_object_get(root, "permanent");
		gboolean save = permanent ? json_is_true(permanent) : FALSE;
		if(save && config == NULL) {
			JANUS_LOG(LOG_ERR, "No configuration file, can't create permanent room\n");
			error_code = JANUS_AUDIOBRIDGE_ERROR_UNKNOWN_ERROR;
			g_snprintf(error_cause, 512, "No configuration file, can't create permanent room");
			goto plugin_response;
		}
		guint64 room_id = 0;
		json_t *room = json_object_get(root, "room");
		room_id = json_integer_value(room);
		if(room_id == 0) {
			JANUS_LOG(LOG_WARN, "Desired room ID is 0, which is not allowed... picking random ID instead\n");
		}
		janus_mutex_lock(&rooms_mutex);
		if(room_id > 0) {
			/* Let's make sure the room doesn't exist already */
			if(g_hash_table_lookup(rooms, &room_id) != NULL) {
				/* It does... */
				janus_mutex_unlock(&rooms_mutex);
				JANUS_LOG(LOG_ERR, "Room %"SCNu64" already exists!\n", room_id);
				error_code = JANUS_AUDIOBRIDGE_ERROR_ROOM_EXISTS;
				g_snprintf(error_cause, 512, "Room %"SCNu64" already exists", room_id);
				goto plugin_response;
			}
		}
		/* Create the audio bridge room */
		janus_audiobridge_room *audiobridge = g_malloc0(sizeof(janus_audiobridge_room));
		/* Generate a random ID */
		if(room_id == 0) {
			while(room_id == 0) {
				room_id = janus_random_uint64();
				if(g_hash_table_lookup(rooms, &room_id) != NULL) {
					/* Room ID already taken, try another one */
					room_id = 0;
				}
			}
		}
		audiobridge->room_id = room_id;
		char *description = NULL;
		if(desc != NULL && strlen(json_string_value(desc)) > 0) {
			description = g_strdup(json_string_value(desc));
		} else {
			char roomname[255];
			g_snprintf(roomname, 255, "Room %"SCNu64"", audiobridge->room_id);
			description = g_strdup(roomname);
		}
		audiobridge->room_name = description;
		audiobridge->is_private = is_private ? json_is_true(is_private) : FALSE;
		if(secret)
			audiobridge->room_secret = g_strdup(json_string_value(secret));
		if(pin)
			audiobridge->room_pin = g_strdup(json_string_value(pin));
		if(sampling)
			audiobridge->sampling_rate = json_integer_value(sampling);
		else
			audiobridge->sampling_rate = 16000;
		switch(audiobridge->sampling_rate) {
			case 8000:
			case 12000:
			case 16000:
			case 24000:
			case 48000:
				JANUS_LOG(LOG_VERB, "Sampling rate for mixing: %"SCNu32"\n", audiobridge->sampling_rate);
				break;
			default:
				janus_mutex_unlock(&rooms_mutex);
				JANUS_LOG(LOG_ERR, "Unsupported sampling rate %"SCNu32"...\n", audiobridge->sampling_rate);
				error_code = JANUS_AUDIOBRIDGE_ERROR_UNKNOWN_ERROR;
				g_snprintf(error_cause, 512, "We currently only support 16kHz (wideband) as a sampling rate for audio rooms, %"SCNu32" TBD...", audiobridge->sampling_rate);
				goto plugin_response;
		}
		audiobridge->record = FALSE;
		if(record && json_is_true(record))
			audiobridge->record = TRUE;
		if(recfile)
			audiobridge->record_file = g_strdup(json_string_value(recfile));
		audiobridge->recording = NULL;
		audiobridge->destroy = 0;
		audiobridge->participants = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, NULL);
		audiobridge->destroyed = 0;
		janus_mutex_init(&audiobridge->mutex);
		g_hash_table_insert(rooms, janus_uint64_dup(audiobridge->room_id), audiobridge);
		JANUS_LOG(LOG_VERB, "Created audiobridge: %"SCNu64" (%s, %s, secret: %s, pin: %s)\n",
			audiobridge->room_id, audiobridge->room_name,
			audiobridge->is_private ? "private" : "public",
			audiobridge->room_secret ? audiobridge->room_secret : "no secret",
			audiobridge->room_pin ? audiobridge->room_pin : "no pin");
		/* We need a thread for the mix */
		GError *error = NULL;
		char tname[16];
		g_snprintf(tname, sizeof(tname), "mixer %"SCNu64, audiobridge->room_id);
		audiobridge->thread = g_thread_try_new(tname, &janus_audiobridge_mixer_thread, audiobridge, &error);
		if(error != NULL) {
			janus_mutex_unlock(&rooms_mutex);
			JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the mixer thread...\n", error->code, error->message ? error->message : "??");
			error_code = JANUS_AUDIOBRIDGE_ERROR_UNKNOWN_ERROR;
			g_snprintf(error_cause, 512, "Got error %d (%s) trying to launch the mixer thread", error->code, error->message ? error->message : "??");
			g_free(audiobridge->room_name);
			g_free(audiobridge->room_secret);
			g_free(audiobridge->record_file);
			g_hash_table_destroy(audiobridge->participants);
			g_free(audiobridge);
			goto plugin_response;
		} else {
			g_hash_table_insert(rooms, janus_uint64_dup(audiobridge->room_id), audiobridge);
		}
		if(save) {
			/* This room is permanent: save to the configuration file too
			 * FIXME: We should check if anything fails... */
			JANUS_LOG(LOG_VERB, "Saving room %"SCNu64" permanently in config file\n", audiobridge->room_id);
			janus_mutex_lock(&config_mutex);
			char cat[BUFSIZ], value[BUFSIZ];
			/* The room ID is the category */
			g_snprintf(cat, BUFSIZ, "%"SCNu64, audiobridge->room_id);
			janus_config_add_category(config, cat);
			/* Now for the values */
			janus_config_add_item(config, cat, "description", audiobridge->room_name);
			if(audiobridge->is_private)
				janus_config_add_item(config, cat, "is_private", "yes");
			g_snprintf(value, BUFSIZ, "%"SCNu32, audiobridge->sampling_rate);
			janus_config_add_item(config, cat, "sampling_rate", value);
			if(audiobridge->room_secret)
				janus_config_add_item(config, cat, "secret", audiobridge->room_secret);
			if(audiobridge->room_pin)
				janus_config_add_item(config, cat, "pin", audiobridge->room_pin);
			if(audiobridge->record_file) {
				janus_config_add_item(config, cat, "record", "yes");
				janus_config_add_item(config, cat, "record_file", audiobridge->record_file);
			}
			/* Save modified configuration */
			janus_config_save(config, config_folder, JANUS_AUDIOBRIDGE_PACKAGE);
			janus_mutex_unlock(&config_mutex);
		}
		/* Send info back */
		response = json_object();
		json_object_set_new(response, "audiobridge", json_string("created"));
		json_object_set_new(response, "room", json_integer(audiobridge->room_id));
		janus_mutex_unlock(&rooms_mutex);
		goto plugin_response;
	} else if(!strcasecmp(request_text, "destroy")) {
		JANUS_LOG(LOG_VERB, "Attempt to destroy an existing audiobridge room\n");
		JANUS_VALIDATE_JSON_OBJECT(root, destroy_parameters,
			error_code, error_cause, TRUE,
			JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT);
		if(error_code != 0)
			goto plugin_response;
		json_t *room = json_object_get(root, "room");
		json_t *permanent = json_object_get(root, "permanent");
		gboolean save = permanent ? json_is_true(permanent) : FALSE;
		if(save && config == NULL) {
			JANUS_LOG(LOG_ERR, "No configuration file, can't destroy room permanently\n");
			error_code = JANUS_AUDIOBRIDGE_ERROR_UNKNOWN_ERROR;
			g_snprintf(error_cause, 512, "No configuration file, can't destroy room permanently");
			goto plugin_response;
		}
		guint64 room_id = json_integer_value(room);
		janus_mutex_lock(&rooms_mutex);
		janus_audiobridge_room *audiobridge = g_hash_table_lookup(rooms, &room_id);
		if(audiobridge == NULL) {
			janus_mutex_unlock(&rooms_mutex);
			JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
			error_code = JANUS_AUDIOBRIDGE_ERROR_NO_SUCH_ROOM;
			g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
			goto plugin_response;
		}
		janus_mutex_lock(&audiobridge->mutex);
		/* A secret may be required for this action */
		JANUS_CHECK_SECRET(audiobridge->room_secret, root, "secret", error_code, error_cause,
			JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_UNAUTHORIZED);
		if(error_code != 0) {
			janus_mutex_unlock(&audiobridge->mutex);
			janus_mutex_unlock(&rooms_mutex);
			goto plugin_response;
		}
		/* Remove room */
		g_hash_table_remove(rooms, &room_id);
		if(save) {
			/* This change is permanent: save to the configuration file too
			 * FIXME: We should check if anything fails... */
			JANUS_LOG(LOG_VERB, "Destroying room %"SCNu64" permanently in config file\n", room_id);
			janus_mutex_lock(&config_mutex);
			char cat[BUFSIZ];
			/* The room ID is the category */
			g_snprintf(cat, BUFSIZ, "%"SCNu64, room_id);
			janus_config_remove_category(config, cat);
			/* Save modified configuration */
			janus_config_save(config, config_folder, JANUS_AUDIOBRIDGE_PACKAGE);
			janus_mutex_unlock(&config_mutex);
		}
		/* Prepare response/notification */
		response = json_object();
		json_object_set_new(response, "audiobridge", json_string("destroyed"));
		json_object_set_new(response, "room", json_integer(room_id));
		/* Notify all participants that the fun is over, and that they'll be kicked */
		JANUS_LOG(LOG_VERB, "Notifying all participants\n");
		GHashTableIter iter;
		gpointer value;
		g_hash_table_iter_init(&iter, audiobridge->participants);
		while (g_hash_table_iter_next(&iter, NULL, &value)) {
			janus_audiobridge_participant *p = value;
			if(p && p->session) {
				p->room = NULL;
				int ret = gateway->push_event(p->session->handle, &janus_audiobridge_plugin, NULL, response, NULL);
				JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
				/* Get rid of queued packets */
				janus_mutex_lock(&p->qmutex);
				p->active = FALSE;
				while(p->inbuf) {
					GList *first = g_list_first(p->inbuf);
					janus_audiobridge_rtp_relay_packet *pkt = (janus_audiobridge_rtp_relay_packet *)first->data;
					p->inbuf = g_list_remove_link(p->inbuf, first);
					first = NULL;
					if(pkt == NULL)
						continue;
					if(pkt->data)
						g_free(pkt->data);
					pkt->data = NULL;
					g_free(pkt);
					pkt = NULL;
				}
				janus_mutex_unlock(&p->qmutex);
			}
		}
		JANUS_LOG(LOG_VERB, "Waiting for the mixer thread to complete...\n");
		audiobridge->destroyed = janus_get_monotonic_time();
		janus_mutex_unlock(&audiobridge->mutex);
		janus_mutex_unlock(&rooms_mutex);
		g_thread_join(audiobridge->thread);
		/* Done */
		JANUS_LOG(LOG_VERB, "Audiobridge room destroyed\n");
		goto plugin_response;
	} else if(!strcasecmp(request_text, "list")) {
		/* List all rooms (but private ones) and their details (except for the secret, of course...) */
		json_t *list = json_array();
		JANUS_LOG(LOG_VERB, "Request for the list for all video rooms\n");
		janus_mutex_lock(&rooms_mutex);
		GHashTableIter iter;
		gpointer value;
		g_hash_table_iter_init(&iter, rooms);
		while(g_hash_table_iter_next(&iter, NULL, &value)) {
			janus_audiobridge_room *room = value;
			if(!room)
				continue;
			janus_mutex_lock(&room->mutex);
			if(room->is_private) {
				/* Skip private room */
				janus_mutex_unlock(&room->mutex);
				JANUS_LOG(LOG_VERB, "Skipping private room '%s'\n", room->room_name);
				continue;
			}
			json_t *rl = json_object();
			json_object_set_new(rl, "room", json_integer(room->room_id));
			json_object_set_new(rl, "description", json_string(room->room_name));
			json_object_set_new(rl, "sampling_rate", json_integer(room->sampling_rate));
			json_object_set_new(rl, "record", room->record ? json_true() : json_false());
			/* TODO: Possibly list participant details... or make it a separate API call for a specific room */
			json_object_set_new(rl, "num_participants", json_integer(g_hash_table_size(room->participants)));
			json_array_append_new(list, rl);
			janus_mutex_unlock(&room->mutex);
		}
		janus_mutex_unlock(&rooms_mutex);
		response = json_object();
		json_object_set_new(response, "audiobridge", json_string("success"));
		json_object_set_new(response, "list", list);
		goto plugin_response;
	} else if(!strcasecmp(request_text, "exists")) {
		/* Check whether a given room exists or not, returns true/false */	
		JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
			error_code, error_cause, TRUE,
			JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT);
		if(error_code != 0)
			goto plugin_response;
		json_t *room = json_object_get(root, "room");
		guint64 room_id = json_integer_value(room);
		janus_mutex_lock(&rooms_mutex);
		gboolean room_exists = g_hash_table_contains(rooms, &room_id);
		janus_mutex_unlock(&rooms_mutex);
		response = json_object();
		json_object_set_new(response, "audiobridge", json_string("success"));
		json_object_set_new(response, "room", json_integer(room_id));
		json_object_set_new(response, "exists", room_exists ? json_true() : json_false());
		goto plugin_response;
	} else if(!strcasecmp(request_text, "listparticipants")) {
		/* List all participants in a room */	
		JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
			error_code, error_cause, TRUE,
			JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT);
		if(error_code != 0)
			goto plugin_response;
		json_t *room = json_object_get(root, "room");
		guint64 room_id = json_integer_value(room);
		janus_mutex_lock(&rooms_mutex);
		janus_audiobridge_room *audiobridge = g_hash_table_lookup(rooms, &room_id);
		if(audiobridge == NULL) {
			janus_mutex_unlock(&rooms_mutex);
			JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
			error_code = JANUS_AUDIOBRIDGE_ERROR_NO_SUCH_ROOM;
			g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
			goto plugin_response;
		}
		janus_mutex_lock(&audiobridge->mutex);
		if(audiobridge->destroyed) {
			janus_mutex_unlock(&audiobridge->mutex);
			janus_mutex_unlock(&rooms_mutex);
			JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
			error_code = JANUS_AUDIOBRIDGE_ERROR_NO_SUCH_ROOM;
			g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
			goto plugin_response;
		}
		/* Return a list of all participants */
		json_t *list = json_array();
		GHashTableIter iter;
		gpointer value;
		g_hash_table_iter_init(&iter, audiobridge->participants);
		while (!audiobridge->destroyed && g_hash_table_iter_next(&iter, NULL, &value)) {
			janus_audiobridge_participant *p = value;
			json_t *pl = json_object();
			json_object_set_new(pl, "id", json_integer(p->user_id));
			if(p->display)
				json_object_set_new(pl, "display", json_string(p->display));
			json_object_set_new(pl, "muted", p->muted ? json_true() : json_false());
			json_array_append_new(list, pl);
		}
		janus_mutex_unlock(&audiobridge->mutex);
		janus_mutex_unlock(&rooms_mutex);
		response = json_object();
		json_object_set_new(response, "audiobridge", json_string("participants"));
		json_object_set_new(response, "room", json_integer(room_id));
		json_object_set_new(response, "participants", list);
		goto plugin_response;
	} else if(!strcasecmp(request_text, "resetdecoder")) {
		/* Mark the Opus decoder for the participant invalid and recreate it */	
		janus_audiobridge_participant *participant = (janus_audiobridge_participant *)session->participant;
		if(participant == NULL || participant->room == NULL) {
			JANUS_LOG(LOG_ERR, "Can't reset (not in a room)\n");
			error_code = JANUS_AUDIOBRIDGE_ERROR_NOT_JOINED;
			g_snprintf(error_cause, 512, "Can't reset (not in a room)");
			goto plugin_response;
		}
		participant->reset = TRUE;
		response = json_object();
		json_object_set_new(response, "audiobridge", json_string("success"));
		goto plugin_response;
	} else if(!strcasecmp(request_text, "join") || !strcasecmp(request_text, "configure")
			|| !strcasecmp(request_text, "changeroom") || !strcasecmp(request_text, "leave")) {
		/* These messages are handled asynchronously */
		janus_audiobridge_message *msg = g_malloc0(sizeof(janus_audiobridge_message));
		msg->handle = handle;
		msg->transaction = transaction;
		msg->message = root;
		msg->jsep = jsep;

		g_async_queue_push(messages, msg);

		return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
	} else {
		JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
		error_code = JANUS_AUDIOBRIDGE_ERROR_INVALID_REQUEST;
		g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
	}

plugin_response:
		{
			if(error_code == 0 && !response) {
				error_code = JANUS_AUDIOBRIDGE_ERROR_UNKNOWN_ERROR;
				g_snprintf(error_cause, 512, "Invalid response");
			}
			if(error_code != 0) {
				/* Prepare JSON error event */
				json_t *event = json_object();
				json_object_set_new(event, "audiobridge", json_string("event"));
				json_object_set_new(event, "error_code", json_integer(error_code));
				json_object_set_new(event, "error", json_string(error_cause));
				response = event;
			}
			if(root != NULL)
				json_decref(root);
			if(jsep != NULL)
				json_decref(jsep);
			g_free(transaction);

			return janus_plugin_result_new(JANUS_PLUGIN_OK, NULL, response);
		}

}

void janus_audiobridge_setup_media(janus_plugin_session *handle) {
	JANUS_LOG(LOG_INFO, "WebRTC media is now available\n");
	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
		return;
	janus_audiobridge_session *session = (janus_audiobridge_session *)handle->plugin_handle;	
	if(!session) {
		JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
		return;
	}
	if(session->destroyed)
		return;
	janus_audiobridge_participant *participant = (janus_audiobridge_participant *)session->participant;
	if(!participant)
		return;
	g_atomic_int_set(&session->hangingup, 0);
	/* FIXME Only send this peer the audio mix when we get this event */
	session->started = TRUE;
}

void janus_audiobridge_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
	if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
		return;
	janus_audiobridge_session *session = (janus_audiobridge_session *)handle->plugin_handle;	
	if(!session || session->destroyed || session->stopping || !session->participant)
		return;
	janus_audiobridge_participant *participant = (janus_audiobridge_participant *)session->participant;
	if(!participant->active || participant->muted || !participant->decoder || !participant->room)
		return;
	/* Save the frame if we're recording this leg */
	janus_recorder_save_frame(participant->arc, buf, len);
	if(participant->active && participant->decoder) {
		/* First of all, check if a reset on the decoder is due */
		if(participant->reset) {
			/* Create a new decoder and get rid of the old one */
			int error = 0;
			OpusDecoder *decoder = opus_decoder_create(participant->room->sampling_rate, 1, &error);
			if(error != OPUS_OK) {
				JANUS_LOG(LOG_ERR, "Error resetting Opus decoder...\n");
			} else {
				if(participant->decoder)
					opus_decoder_destroy(participant->decoder);
				participant->decoder = decoder;
				JANUS_LOG(LOG_VERB, "Opus decoder reset\n");
			}
			participant->reset = FALSE;
		}
		/* Decode frame (Opus -> slinear) */
		rtp_header *rtp = (rtp_header *)buf;
		janus_audiobridge_rtp_relay_packet *pkt = g_malloc0(sizeof(janus_audiobridge_rtp_relay_packet));
		pkt->data = g_malloc0(BUFFER_SAMPLES*sizeof(opus_int16));
		pkt->ssrc = 0;
		pkt->timestamp = ntohl(rtp->timestamp);
		pkt->seq_number = ntohs(rtp->seq_number);
		participant->working = TRUE;
		pkt->length = opus_decode(participant->decoder, (const unsigned char *)buf+12, len-12, (opus_int16 *)pkt->data, BUFFER_SAMPLES, USE_FEC);
		participant->working = FALSE;
		if(pkt->length < 0) {
			JANUS_LOG(LOG_ERR, "[Opus] Ops! got an error decoding the Opus frame: %d (%s)\n", pkt->length, opus_strerror(pkt->length));
			g_free(pkt->data);
			g_free(pkt);
			return;
		}
		/* Enqueue the decoded frame */
		janus_mutex_lock(&participant->qmutex);
		/* Insert packets sorting by sequence number */
		participant->inbuf = g_list_insert_sorted(participant->inbuf, pkt, &janus_audiobridge_rtp_sort);
		if(participant->prebuffering) {
			/* Still pre-buffering: do we have enough packets now? */
			if(g_list_length(participant->inbuf) == DEFAULT_PREBUFFERING) {
				participant->prebuffering = FALSE;
				JANUS_LOG(LOG_VERB, "Prebuffering done! Finally adding the user to the mix\n");
			} else {
				JANUS_LOG(LOG_VERB, "Still prebuffering (got %d packets), not adding the user to the mix yet\n", g_list_length(participant->inbuf));
			}
		} else {
			/* Make sure we're not queueing too many packets: if so, get rid of the older ones */
			if(g_list_length(participant->inbuf) >= DEFAULT_PREBUFFERING*2) {
				JANUS_LOG(LOG_WARN, "Too many packets in queue (%d > %d), removing older ones\n",
					g_list_length(participant->inbuf), DEFAULT_PREBUFFERING*2);
				while(g_list_length(participant->inbuf) > DEFAULT_PREBUFFERING*2) {
					/* Remove this packet: it's too old */
					GList *first = g_list_first(participant->inbuf);
					janus_audiobridge_rtp_relay_packet *pkt = (janus_audiobridge_rtp_relay_packet *)first->data;
					participant->inbuf = g_list_remove_link(participant->inbuf, first);
					first = NULL;
					if(pkt == NULL)
						continue;
					if(pkt->data)
						g_free(pkt->data);
					pkt->data = NULL;
					g_free(pkt);
					pkt = NULL;
				}
			}
		}
		janus_mutex_unlock(&participant->qmutex);
	}
}

void janus_audiobridge_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
	if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
		return;
	/* FIXME Should we care? */
}

void janus_audiobridge_hangup_media(janus_plugin_session *handle) {
	JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n");
	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
		return;
	janus_audiobridge_session *session = (janus_audiobridge_session *)handle->plugin_handle;
	if(!session) {
		JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
		return;
	}
	session->started = FALSE;
	if(session->destroyed || !session->participant)
		return;
	if(g_atomic_int_add(&session->hangingup, 1))
		return;
	/* Get rid of participant */
	janus_audiobridge_participant *participant = (janus_audiobridge_participant *)session->participant;
	janus_mutex_lock(&rooms_mutex);
	janus_audiobridge_room *audiobridge = participant->room;
	if(audiobridge != NULL) {
		janus_mutex_lock(&audiobridge->mutex);
		json_t *event = json_object();
		json_object_set_new(event, "audiobridge", json_string("event"));
		json_object_set_new(event, "room", json_integer(audiobridge->room_id));
		json_object_set_new(event, "leaving", json_integer(participant->user_id));
		g_hash_table_remove(audiobridge->participants, &participant->user_id);
		GHashTableIter iter;
		gpointer value;
		g_hash_table_iter_init(&iter, audiobridge->participants);
		while (g_hash_table_iter_next(&iter, NULL, &value)) {
			janus_audiobridge_participant *p = value;
			if(p == participant) {
				continue;	/* Skip the leaving participant itself */
			}
			JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
			int ret = gateway->push_event(p->session->handle, &janus_audiobridge_plugin, NULL, event, NULL);
			JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
		}
		json_decref(event);
		janus_mutex_unlock(&audiobridge->mutex);
	}
	/* Get rid of the recorders, if available */
	janus_mutex_lock(&participant->rec_mutex);
	if(participant->arc) {
		janus_recorder_close(participant->arc);
		JANUS_LOG(LOG_INFO, "Closed user's audio recording %s\n", participant->arc->filename ? participant->arc->filename : "??");
		janus_recorder_free(participant->arc);
	}
	participant->arc = NULL;
	janus_mutex_unlock(&participant->rec_mutex);
	/* Free the participant resources */
	janus_mutex_lock(&participant->qmutex);
	participant->active = FALSE;
	participant->muted = TRUE;
	if(participant->display)
		g_free(participant->display);
	participant->display = NULL;
	participant->prebuffering = TRUE;
	/* Make sure we're not using the encoder/decoder right now, we're going to destroy them */
	while(participant->working)
		g_usleep(5000);
	if(participant->encoder)
		opus_encoder_destroy(participant->encoder);
	participant->encoder = NULL;
	if(participant->decoder)
		opus_decoder_destroy(participant->decoder);
	participant->decoder = NULL;
	participant->reset = FALSE;
	/* Get rid of queued packets */
	while(participant->inbuf) {
		GList *first = g_list_first(participant->inbuf);
		janus_audiobridge_rtp_relay_packet *pkt = (janus_audiobridge_rtp_relay_packet *)first->data;
		participant->inbuf = g_list_remove_link(participant->inbuf, first);
		first = NULL;
		if(pkt == NULL)
			continue;
		if(pkt->data)
			g_free(pkt->data);
		pkt->data = NULL;
		g_free(pkt);
		pkt = NULL;
	}
	janus_mutex_unlock(&participant->qmutex);
	if(audiobridge != NULL) {
		janus_mutex_unlock(&audiobridge->mutex);
	}
	janus_mutex_unlock(&rooms_mutex);
}

/* Thread to handle incoming messages */
static void *janus_audiobridge_handler(void *data) {
	JANUS_LOG(LOG_VERB, "Joining AudioBridge handler thread\n");
	janus_audiobridge_message *msg = NULL;
	int error_code = 0;
	char error_cause[512];
	json_t *root = NULL;
	while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
		msg = g_async_queue_pop(messages);
		if(msg == NULL)
			continue;
		if(msg == &exit_message)
			break;
		if(msg->handle == NULL) {
			janus_audiobridge_message_free(msg);
			continue;
		}
		janus_audiobridge_session *session = NULL;
		janus_mutex_lock(&sessions_mutex);
		if(g_hash_table_lookup(sessions, msg->handle) != NULL) {
			session = (janus_audiobridge_session *)msg->handle->plugin_handle;
		}
		janus_mutex_unlock(&sessions_mutex);
		if(!session) {
			JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
			janus_audiobridge_message_free(msg);
			continue;
		}
		if(session->destroyed) {
			janus_audiobridge_message_free(msg);
			continue;
		}
		/* Handle request */
		error_code = 0;
		root = NULL;
		if(msg->message == NULL) {
			JANUS_LOG(LOG_ERR, "No message??\n");
			error_code = JANUS_AUDIOBRIDGE_ERROR_NO_MESSAGE;
			g_snprintf(error_cause, 512, "%s", "No message??");
			goto error;
		}
		root = msg->message;
		/* Get the request first */
		JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
			error_code, error_cause, TRUE,
			JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT);
		if(error_code != 0)
			goto error;
		json_t *request = json_object_get(root, "request");
		const char *request_text = json_string_value(request);
		json_t *event = NULL;
		if(!strcasecmp(request_text, "join")) {
			JANUS_LOG(LOG_VERB, "Configuring new participant\n");
			janus_audiobridge_participant *participant = session->participant;
			if(participant != NULL && participant->room != NULL) {
				JANUS_LOG(LOG_ERR, "Already in a room (use changeroom to join another one)\n");
				error_code = JANUS_AUDIOBRIDGE_ERROR_ALREADY_JOINED;
				g_snprintf(error_cause, 512, "Already in a room (use changeroom to join another one)");
				goto error;
			}
			JANUS_VALIDATE_JSON_OBJECT(root, join_parameters,
				error_code, error_cause, TRUE,
				JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT);
			if(error_code != 0)
				goto error;
			json_t *room = json_object_get(root, "room");
			guint64 room_id = json_integer_value(room);
			janus_mutex_lock(&rooms_mutex);
			janus_audiobridge_room *audiobridge = g_hash_table_lookup(rooms, &room_id);
			if(audiobridge == NULL) {
				janus_mutex_unlock(&rooms_mutex);
				JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
				error_code = JANUS_AUDIOBRIDGE_ERROR_NO_SUCH_ROOM;
				g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
				goto error;
			}
			janus_mutex_lock(&audiobridge->mutex);
			/* A pin may be required for this action */
			JANUS_CHECK_SECRET(audiobridge->room_pin, root, "pin", error_code, error_cause,
				JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_UNAUTHORIZED);
			if(error_code != 0) {
				janus_mutex_unlock(&audiobridge->mutex);
				janus_mutex_unlock(&rooms_mutex);
				goto error;
			}
			json_t *display = json_object_get(root, "display");
			const char *display_text = display ? json_string_value(display) : NULL;
			json_t *muted = json_object_get(root, "muted");
			json_t *quality = json_object_get(root, "quality");
			int complexity = quality ? json_integer_value(quality) : DEFAULT_COMPLEXITY;
			if(complexity < 1 || complexity > 10) {
				janus_mutex_unlock(&audiobridge->mutex);
				janus_mutex_unlock(&rooms_mutex);
				JANUS_LOG(LOG_ERR, "Invalid element (quality should be a positive integer between 1 and 10)\n");
				error_code = JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT;
				g_snprintf(error_cause, 512, "Invalid element (quality should be a positive integer between 1 and 10)");
				goto error;
			}
			guint64 user_id = 0;
			json_t *id = json_object_get(root, "id");
			if(id) {
				user_id = json_integer_value(id);
				if(g_hash_table_lookup(audiobridge->participants, &user_id) != NULL) {
					/* User ID already taken */
					janus_mutex_unlock(&audiobridge->mutex);
					janus_mutex_unlock(&rooms_mutex);
					JANUS_LOG(LOG_ERR, "User ID %"SCNu64" already exists\n", user_id);
					error_code = JANUS_AUDIOBRIDGE_ERROR_ID_EXISTS;
					g_snprintf(error_cause, 512, "User ID %"SCNu64" already exists", user_id);
					goto error;
				}
			}
			if(user_id == 0) {
				/* Generate a random ID */
				while(user_id == 0) {
					user_id = janus_random_uint64();
					if(g_hash_table_lookup(audiobridge->participants, &user_id) != NULL) {
						/* User ID already taken, try another one */
						user_id = 0;
					}
				}
			}
			JANUS_LOG(LOG_VERB, "  -- Participant ID: %"SCNu64"\n", user_id);
			if(participant == NULL) {
				participant = g_malloc0(sizeof(janus_audiobridge_participant));
				participant->active = FALSE;
				participant->prebuffering = TRUE;
				participant->display = NULL;
				participant->inbuf = NULL;
				participant->outbuf = NULL;
				participant->encoder = NULL;
				participant->decoder = NULL;
				participant->reset = FALSE;
				janus_mutex_init(&participant->qmutex);
				participant->arc = NULL;
				janus_mutex_init(&participant->rec_mutex);
			}
			participant->session = session;
			participant->room = audiobridge;
			participant->user_id = user_id;
			if(participant->display != NULL)
				g_free(participant->display);
			participant->display = display_text ? g_strdup(display_text) : NULL;
			participant->muted = muted ? json_is_true(muted) : FALSE;	/* By default, everyone's unmuted when joining */
			participant->opus_complexity = complexity;
			if(participant->outbuf == NULL)
				participant->outbuf = g_async_queue_new();
			participant->active = session->started;
			if(!session->started) {
				/* Initialize the RTP context only if we're renegotiating */
				participant->context.a_last_ssrc = 0;
				participant->context.a_last_ts = 0;
				participant->context.a_base_ts = 0;
				participant->context.a_base_ts_prev = 0;
				participant->context.a_last_seq = 0;
				participant->context.a_base_seq = 0;
				participant->context.a_base_seq_prev = 0;
				participant->opus_pt = 0;
			}
			JANUS_LOG(LOG_VERB, "Creating Opus encoder/decoder (sampling rate %d)\n", audiobridge->sampling_rate);
			/* Opus encoder */
			int error = 0;
			if(participant->encoder == NULL) {
				participant->encoder = opus_encoder_create(audiobridge->sampling_rate, 1, OPUS_APPLICATION_VOIP, &error);
				if(error != OPUS_OK) {
					janus_mutex_unlock(&audiobridge->mutex);
					janus_mutex_unlock(&rooms_mutex);
					if(participant->display)
						g_free(participant->display);
					g_free(participant);
					JANUS_LOG(LOG_ERR, "Error creating Opus encoder\n");
					error_code = JANUS_AUDIOBRIDGE_ERROR_LIBOPUS_ERROR;
					g_snprintf(error_cause, 512, "Error creating Opus decoder");
					goto error;
				}
				if(audiobridge->sampling_rate == 8000) {
					opus_encoder_ctl(participant->encoder, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_NARROWBAND));
				} else if(audiobridge->sampling_rate == 12000) {
					opus_encoder_ctl(participant->encoder, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_MEDIUMBAND));
				} else if(audiobridge->sampling_rate == 16000) {
					opus_encoder_ctl(participant->encoder, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_WIDEBAND));
				} else if(audiobridge->sampling_rate == 24000) {
					opus_encoder_ctl(participant->encoder, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_SUPERWIDEBAND));
				} else if(audiobridge->sampling_rate == 48000) {
					opus_encoder_ctl(participant->encoder, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND));
				} else {
					JANUS_LOG(LOG_WARN, "Unsupported sampling rate %d, setting 16kHz\n", audiobridge->sampling_rate);
					audiobridge->sampling_rate = 16000;
					opus_encoder_ctl(participant->encoder, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_WIDEBAND));
				}
				/* FIXME This settings should be configurable */
				opus_encoder_ctl(participant->encoder, OPUS_SET_INBAND_FEC(USE_FEC));
			}
			opus_encoder_ctl(participant->encoder, OPUS_SET_COMPLEXITY(participant->opus_complexity));
			if(participant->decoder == NULL) {
				/* Opus decoder */
				error = 0;
				participant->decoder = opus_decoder_create(audiobridge->sampling_rate, 1, &error);
				if(error != OPUS_OK) {
					janus_mutex_unlock(&audiobridge->mutex);
					janus_mutex_unlock(&rooms_mutex);
					if(participant->display)
						g_free(participant->display);
					if(participant->encoder)
						opus_encoder_destroy(participant->encoder);
					participant->encoder = NULL;
					if(participant->decoder)
						opus_decoder_destroy(participant->decoder);
					participant->decoder = NULL;
					g_free(participant);
					JANUS_LOG(LOG_ERR, "Error creating Opus encoder\n");
					error_code = JANUS_AUDIOBRIDGE_ERROR_LIBOPUS_ERROR;
					g_snprintf(error_cause, 512, "Error creating Opus decoder");
					goto error;
				}
			}
			participant->reset = FALSE;
			/* Finally, start the encoding thread if it hasn't already */
			if(participant->thread == NULL) {
				GError *error = NULL;
				char roomtrunc[5], parttrunc[5];
				g_snprintf(roomtrunc, sizeof(roomtrunc), "%"SCNu64, audiobridge->room_id);
				g_snprintf(parttrunc, sizeof(parttrunc), "%"SCNu64, participant->user_id);
				char tname[16];
				g_snprintf(tname, sizeof(tname), "mixer %s %s", roomtrunc, parttrunc);
				participant->thread = g_thread_try_new(tname, &janus_audiobridge_participant_thread, participant, &error);
				if(error != NULL) {
					/* FIXME We should fail here... */
					JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the participant thread...\n", error->code, error->message ? error->message : "??");
				}
			}
			
			/* Done */
			session->participant = participant;
			g_hash_table_insert(audiobridge->participants, janus_uint64_dup(participant->user_id), participant);
			/* Notify the other participants */
			json_t *newuser = json_object();
			json_object_set_new(newuser, "audiobridge", json_string("joined"));
			json_object_set_new(newuser, "room", json_integer(room_id));
			json_t *newuserlist = json_array();
			json_t *pl = json_object();
			json_object_set_new(pl, "id", json_integer(participant->user_id));
			if(participant->display)
				json_object_set_new(pl, "display", json_string(participant->display));
			json_object_set_new(pl, "muted", participant->muted ? json_true() : json_false());
			json_array_append_new(newuserlist, pl);
			json_object_set_new(newuser, "participants", newuserlist);
			GHashTableIter iter;
			gpointer value;
			g_hash_table_iter_init(&iter, audiobridge->participants);
			while (g_hash_table_iter_next(&iter, NULL, &value)) {
				janus_audiobridge_participant *p = value;
				if(p == participant) {
					continue;
				}
				JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
				int ret = gateway->push_event(p->session->handle, &janus_audiobridge_plugin, NULL, newuser, NULL);
				JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
			}
			json_decref(newuser);
			/* Return a list of all available participants for the new participant now */
			json_t *list = json_array();
			g_hash_table_iter_init(&iter, audiobridge->participants);
			while (g_hash_table_iter_next(&iter, NULL, &value)) {
				janus_audiobridge_participant *p = value;
				if(p == participant) {
					continue;
				}
				json_t *pl = json_object();
				json_object_set_new(pl, "id", json_integer(p->user_id));
				if(p->display)
					json_object_set_new(pl, "display", json_string(p->display));
				json_object_set_new(pl, "muted", p->muted ? json_true() : json_false());
				json_array_append_new(list, pl);
			}
			janus_mutex_unlock(&audiobridge->mutex);
			janus_mutex_unlock(&rooms_mutex);
			event = json_object();
			json_object_set_new(event, "audiobridge", json_string("joined"));
			json_object_set_new(event, "room", json_integer(room_id));
			json_object_set_new(event, "id", json_integer(user_id));
			json_object_set_new(event, "participants", list);
		} else if(!strcasecmp(request_text, "configure")) {
			/* Handle this participant */
			janus_audiobridge_participant *participant = (janus_audiobridge_participant *)session->participant;
			if(participant == NULL || participant->room == NULL) {
				JANUS_LOG(LOG_ERR, "Can't configure (not in a room)\n");
				error_code = JANUS_AUDIOBRIDGE_ERROR_NOT_JOINED;
				g_snprintf(error_cause, 512, "Can't configure (not in a room)");
				goto error;
			}
			/* Configure settings for this participant */
			JANUS_VALIDATE_JSON_OBJECT(root, configure_parameters,
				error_code, error_cause, TRUE,
				JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT);
			if(error_code != 0)
				goto error;
			json_t *muted = json_object_get(root, "muted");
			json_t *quality = json_object_get(root, "quality");
			json_t *record = json_object_get(root, "record");
			json_t *recfile = json_object_get(root, "filename");
			if(quality) {
				int complexity = quality ? json_integer_value(quality) : DEFAULT_COMPLEXITY;
				if(complexity < 1 || complexity > 10) {
					JANUS_LOG(LOG_ERR, "Invalid element (quality should be a positive integer between 1 and 10)\n");
					error_code = JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT;
					g_snprintf(error_cause, 512, "Invalid element (quality should be a positive integer between 1 and 10)");
					goto error;
				}
				participant->opus_complexity = complexity;
				if(participant->encoder)
					opus_encoder_ctl(participant->encoder, OPUS_SET_COMPLEXITY(participant->opus_complexity));
			}
			if(muted) {
				participant->muted = json_is_true(muted);
				JANUS_LOG(LOG_VERB, "Setting muted property: %s (room %"SCNu64", user %"SCNu64")\n", participant->muted ? "true" : "false", participant->room->room_id, participant->user_id);
				if(participant->muted) {
					/* Clear the queued packets waiting to be handled */
					janus_mutex_lock(&participant->qmutex);
					while(participant->inbuf) {
						GList *first = g_list_first(participant->inbuf);
						janus_audiobridge_rtp_relay_packet *pkt = (janus_audiobridge_rtp_relay_packet *)first->data;
						participant->inbuf = g_list_remove_link(participant->inbuf, first);
						first = NULL;
						if(pkt == NULL)
							continue;
						if(pkt->data)
							g_free(pkt->data);
						pkt->data = NULL;
						g_free(pkt);
						pkt = NULL;
					}
					janus_mutex_unlock(&participant->qmutex);
				}
				/* Notify all other participants about the mute/unmute */
				janus_mutex_lock(&rooms_mutex);
				janus_audiobridge_room *audiobridge = participant->room;
				if(audiobridge != NULL) {
					janus_mutex_lock(&audiobridge->mutex);
					json_t *list = json_array();
					json_t *pl = json_object();
					json_object_set_new(pl, "id", json_integer(participant->user_id));
					if(participant->display)
						json_object_set_new(pl, "display", json_string(participant->display));
					json_object_set_new(pl, "muted", participant->muted ? json_true() : json_false());
					json_array_append_new(list, pl);
					json_t *pub = json_object();
					json_object_set_new(pub, "audiobridge", json_string("event"));
					json_object_set_new(pub, "room", json_integer(participant->room->room_id));
					json_object_set_new(pub, "participants", list);
					GHashTableIter iter;
					gpointer value;
					g_hash_table_iter_init(&iter, audiobridge->participants);
					while (g_hash_table_iter_next(&iter, NULL, &value)) {
						janus_audiobridge_participant *p = value;
						if(p == participant) {
							continue;	/* Skip the new participant itself */
						}
						JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
						int ret = gateway->push_event(p->session->handle, &janus_audiobridge_plugin, NULL, pub, NULL);
						JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
					}
					json_decref(pub);
					janus_mutex_unlock(&audiobridge->mutex);
				}
				janus_mutex_unlock(&rooms_mutex);
			}
			if(record) {
				janus_mutex_lock(&participant->rec_mutex);
				if(json_is_true(record)) {
					/* Start recording (ignore if recording already) */
					if(participant->arc != NULL) {
						JANUS_LOG(LOG_WARN, "Already recording participant's audio (room %"SCNu64", user %"SCNu64")\n",
							participant->user_id, participant->room->room_id);
					} else {
						JANUS_LOG(LOG_INFO, "Starting recording of participant's audio (room %"SCNu64", user %"SCNu64")\n",
							participant->user_id, participant->room->room_id);
						char filename[255];
						gint64 now = janus_get_real_time();
						memset(filename, 0, 255);
						const char *recording_base = json_string_value(recfile);
						if(recording_base) {
							/* Use the filename and path we have been provided */
							g_snprintf(filename, 255, "%s-audio", recording_base);
							participant->arc = janus_recorder_create(NULL, "opus", filename);
							if(participant->arc == NULL) {
								/* FIXME We should notify the fact the recorder could not be created */
								JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this participant!\n");
							}
						} else {
							/* Build a filename */
							g_snprintf(filename, 255, "audiobridge-%"SCNu64"-%"SCNu64"-%"SCNi64"-audio",
								participant->user_id, participant->room->room_id, now);
							participant->arc = janus_recorder_create(NULL, "opus", filename);
							if(participant->arc == NULL) {
								/* FIXME We should notify the fact the recorder could not be created */
								JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this participant!\n");
							}
						}
					}
				} else {
					/* Stop recording (ignore if not recording) */
					if(participant->arc) {
						janus_recorder_close(participant->arc);
						JANUS_LOG(LOG_INFO, "Closed user's audio recording %s\n", participant->arc->filename ? participant->arc->filename : "??");
						janus_recorder_free(participant->arc);
					}
					participant->arc = NULL;
				}
				janus_mutex_unlock(&participant->rec_mutex);
			}
			/* Done */
			event = json_object();
			json_object_set_new(event, "audiobridge", json_string("event"));
			json_object_set_new(event, "result", json_string("ok"));
		} else if(!strcasecmp(request_text, "changeroom")) {
			/* The participant wants to leave the current room and join another one without reconnecting (e.g., a sidebar) */
			janus_audiobridge_participant *participant = (janus_audiobridge_participant *)session->participant;
			if(participant == NULL || participant->room == NULL) {
				JANUS_LOG(LOG_ERR, "Can't change room (not in a room in the first place)\n");
				error_code = JANUS_AUDIOBRIDGE_ERROR_NOT_JOINED;
				g_snprintf(error_cause, 512, "Can't change room (not in a room in the first place)");
				goto error;
			}
			JANUS_VALIDATE_JSON_OBJECT(root, join_parameters,
				error_code, error_cause, TRUE,
				JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT);
			if(error_code != 0)
				goto error;
			json_t *room = json_object_get(root, "room");
			guint64 room_id = json_integer_value(room);
			janus_mutex_lock(&rooms_mutex);
			/* Is this the same room we're in? */
			if(participant->room && participant->room->room_id == room_id) {
				janus_mutex_unlock(&rooms_mutex);
				JANUS_LOG(LOG_ERR, "Already in this room\n");
				error_code = JANUS_AUDIOBRIDGE_ERROR_ALREADY_JOINED;
				g_snprintf(error_cause, 512, "Already in this room");
				goto error;
			}
			janus_audiobridge_room *audiobridge = g_hash_table_lookup(rooms, &room_id);
			if(audiobridge == NULL) {
				janus_mutex_unlock(&rooms_mutex);
				JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
				error_code = JANUS_AUDIOBRIDGE_ERROR_NO_SUCH_ROOM;
				g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
				goto error;
			}
			janus_mutex_lock(&audiobridge->mutex);
			/* A pin may be required for this action */
			JANUS_CHECK_SECRET(audiobridge->room_pin, root, "pin", error_code, error_cause,
				JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_UNAUTHORIZED);
			if(error_code != 0) {
				janus_mutex_unlock(&audiobridge->mutex);
				janus_mutex_unlock(&rooms_mutex);
				goto error;
			}
			json_t *display = json_object_get(root, "display");
			const char *display_text = display ? json_string_value(display) : NULL;
			json_t *muted = json_object_get(root, "muted");
			json_t *quality = json_object_get(root, "quality");
			int complexity = quality ? json_integer_value(quality) : DEFAULT_COMPLEXITY;
			if(complexity < 1 || complexity > 10) {
				janus_mutex_unlock(&audiobridge->mutex);
				janus_mutex_unlock(&rooms_mutex);
				JANUS_LOG(LOG_ERR, "Invalid element (quality should be a positive integer between 1 and 10)\n");
				error_code = JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT;
				g_snprintf(error_cause, 512, "Invalid element (quality should be a positive integer between 1 and 10)");
				goto error;
			}
			guint64 user_id = 0;
			json_t *id = json_object_get(root, "id");
			if(id) {
				user_id = json_integer_value(id);
				if(g_hash_table_lookup(audiobridge->participants, &user_id) != NULL) {
					/* User ID already taken */
					janus_mutex_unlock(&audiobridge->mutex);
					janus_mutex_unlock(&rooms_mutex);
					JANUS_LOG(LOG_ERR, "User ID %"SCNu64" already exists\n", user_id);
					error_code = JANUS_AUDIOBRIDGE_ERROR_ID_EXISTS;
					g_snprintf(error_cause, 512, "User ID %"SCNu64" already exists", user_id);
					goto error;
				}
			}
			if(user_id == 0) {
				/* Generate a random ID */
				while(user_id == 0) {
					user_id = janus_random_uint64();
					if(g_hash_table_lookup(audiobridge->participants, &user_id) != NULL) {
						/* User ID already taken, try another one */
						user_id = 0;
					}
				}
			}
			JANUS_LOG(LOG_VERB, "  -- Participant ID in new room %"SCNu64": %"SCNu64"\n", room_id, user_id);
			participant->prebuffering = TRUE;
			/* Is the sampling rate of the new room the same as the one in the old room, or should we update the decoder/encoder? */
			janus_audiobridge_room *old_audiobridge = participant->room;
			/* Leave the old room first... */
			janus_mutex_lock(&old_audiobridge->mutex);
			g_hash_table_remove(old_audiobridge->participants, &participant->user_id);
			if(old_audiobridge->sampling_rate != audiobridge->sampling_rate) {
				/* Create a new one that takes into account the sampling rate we want now */
				int error = 0;
				OpusEncoder *new_encoder = opus_encoder_create(audiobridge->sampling_rate, 1, OPUS_APPLICATION_VOIP, &error);
				if(error != OPUS_OK) {
					if(new_encoder)
						opus_encoder_destroy(new_encoder);
					new_encoder = NULL;
					JANUS_LOG(LOG_ERR, "Error creating Opus encoder\n");
					error_code = JANUS_AUDIOBRIDGE_ERROR_LIBOPUS_ERROR;
					g_snprintf(error_cause, 512, "Error creating Opus decoder");
					/* Join the old room again... */
					g_hash_table_insert(audiobridge->participants, janus_uint64_dup(participant->user_id), participant);
					janus_mutex_unlock(&old_audiobridge->mutex);
					janus_mutex_unlock(&audiobridge->mutex);
					janus_mutex_unlock(&rooms_mutex);
					goto error;
				}
				if(audiobridge->sampling_rate == 8000) {
					opus_encoder_ctl(new_encoder, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_NARROWBAND));
				} else if(audiobridge->sampling_rate == 12000) {
					opus_encoder_ctl(new_encoder, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_MEDIUMBAND));
				} else if(audiobridge->sampling_rate == 16000) {
					opus_encoder_ctl(new_encoder, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_WIDEBAND));
				} else if(audiobridge->sampling_rate == 24000) {
					opus_encoder_ctl(new_encoder, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_SUPERWIDEBAND));
				} else if(audiobridge->sampling_rate == 48000) {
					opus_encoder_ctl(new_encoder, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND));
				} else {
					JANUS_LOG(LOG_WARN, "Unsupported sampling rate %d, setting 16kHz\n", audiobridge->sampling_rate);
					audiobridge->sampling_rate = 16000;
					opus_encoder_ctl(new_encoder, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_WIDEBAND));
				}
				/* FIXME This settings should be configurable */
				opus_encoder_ctl(new_encoder, OPUS_SET_INBAND_FEC(USE_FEC));
				opus_encoder_ctl(new_encoder, OPUS_SET_COMPLEXITY(participant->opus_complexity));
				/* Opus decoder */
				error = 0;
				OpusDecoder *new_decoder = opus_decoder_create(audiobridge->sampling_rate, 1, &error);
				if(error != OPUS_OK) {
					if(new_encoder)
						opus_encoder_destroy(new_encoder);
					new_encoder = NULL;
					if(new_decoder)
						opus_decoder_destroy(new_decoder);
					new_decoder = NULL;
					JANUS_LOG(LOG_ERR, "Error creating Opus encoder\n");
					error_code = JANUS_AUDIOBRIDGE_ERROR_LIBOPUS_ERROR;
					g_snprintf(error_cause, 512, "Error creating Opus decoder");
					/* Join the old room again... */
					g_hash_table_insert(audiobridge->participants, janus_uint64_dup(participant->user_id), participant);
					janus_mutex_unlock(&old_audiobridge->mutex);
					janus_mutex_unlock(&audiobridge->mutex);
					janus_mutex_unlock(&rooms_mutex);
					goto error;
				}
				participant->reset = FALSE;
				/* Destroy the previous encoder/decoder and update the references */
				if(participant->encoder)
					opus_encoder_destroy(participant->encoder);
				participant->encoder = new_encoder;
				if(participant->decoder)
					opus_decoder_destroy(participant->decoder);
				participant->decoder = new_decoder;
			}
			/* Everything looks fine, start by telling the folks in the old room this participant is going away */
			event = json_object();
			json_object_set_new(event, "audiobridge", json_string("event"));
			json_object_set_new(event, "room", json_integer(old_audiobridge->room_id));
			json_object_set_new(event, "leaving", json_integer(participant->user_id));
			GHashTableIter iter;
			gpointer value;
			g_hash_table_iter_init(&iter, old_audiobridge->participants);
			while (g_hash_table_iter_next(&iter, NULL, &value)) {
				janus_audiobridge_participant *p = value;
				if(p == participant) {
					continue;	/* Skip the new participant itself */
				}
				JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
				int ret = gateway->push_event(p->session->handle, &janus_audiobridge_plugin, NULL, event, NULL);
				JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
			}
			json_decref(event);
			janus_mutex_unlock(&old_audiobridge->mutex);
			/* Stop recording, if we were (since this is a new room, a new recording would be required, so a new configure) */
			janus_mutex_lock(&participant->rec_mutex);
			if(participant->arc) {
				janus_recorder_close(participant->arc);
				JANUS_LOG(LOG_INFO, "Closed user's audio recording %s\n", participant->arc->filename ? participant->arc->filename : "??");
				janus_recorder_free(participant->arc);
			}
			participant->arc = NULL;
			janus_mutex_unlock(&participant->rec_mutex);
			/* Done, join the new one */
			participant->user_id = user_id;
			if(display_text) {
				g_free(participant->display);
				participant->display = display_text ? g_strdup(display_text) : NULL;
			}
			participant->room = audiobridge;
			participant->muted = muted ? json_is_true(muted) : FALSE;	/* When switching to a new room, you're unmuted by default */
			if(quality) {
				participant->opus_complexity = complexity;
				if(participant->encoder)
					opus_encoder_ctl(participant->encoder, OPUS_SET_COMPLEXITY(participant->opus_complexity));
			}
			g_hash_table_insert(audiobridge->participants, janus_uint64_dup(participant->user_id), participant);
			/* Notify the other participants */
			json_t *newuser = json_object();
			json_object_set_new(newuser, "audiobridge", json_string("joined"));
			json_object_set_new(newuser, "room", json_integer(audiobridge->room_id));
			json_t *newuserlist = json_array();
			json_t *pl = json_object();
			json_object_set_new(pl, "id", json_integer(participant->user_id));
			if(participant->display)
				json_object_set_new(pl, "display", json_string(participant->display));
			json_object_set_new(pl, "muted", participant->muted ? json_true() : json_false());
			json_array_append_new(newuserlist, pl);
			json_object_set_new(newuser, "participants", newuserlist);
			g_hash_table_iter_init(&iter, audiobridge->participants);
			while (g_hash_table_iter_next(&iter, NULL, &value)) {
				janus_audiobridge_participant *p = value;
				if(p == participant) {
					continue;
				}
				JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
				int ret = gateway->push_event(p->session->handle, &janus_audiobridge_plugin, NULL, newuser, NULL);
				JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
			}
			json_decref(event);
			/* Return a list of all available participants for the new participant now */
			json_t *list = json_array();
			g_hash_table_iter_init(&iter, audiobridge->participants);
			while (g_hash_table_iter_next(&iter, NULL, &value)) {
				janus_audiobridge_participant *p = value;
				if(p == participant) {
					continue;
				}
				json_t *pl = json_object();
				json_object_set_new(pl, "id", json_integer(p->user_id));
				if(p->display)
					json_object_set_new(pl, "display", json_string(p->display));
				json_object_set_new(pl, "muted", p->muted ? json_true() : json_false());
				json_array_append_new(list, pl);
			}
			event = json_object();
			json_object_set_new(event, "audiobridge", json_string("roomchanged"));
			json_object_set_new(event, "room", json_integer(audiobridge->room_id));
			json_object_set_new(event, "id", json_integer(user_id));
			json_object_set_new(event, "participants", list);
			janus_mutex_unlock(&audiobridge->mutex);
			janus_mutex_unlock(&rooms_mutex);
		} else if(!strcasecmp(request_text, "leave")) {
			/* This participant is leaving */
			janus_audiobridge_participant *participant = (janus_audiobridge_participant *)session->participant;
			if(participant == NULL || participant->room == NULL) {
				JANUS_LOG(LOG_ERR, "Can't leave (not in a room)\n");
				error_code = JANUS_AUDIOBRIDGE_ERROR_NOT_JOINED;
				g_snprintf(error_cause, 512, "Can't leave (not in a room)");
				goto error;
			}
			/* Tell everybody */
			janus_mutex_lock(&rooms_mutex);
			janus_audiobridge_room *audiobridge = participant->room;
			if(audiobridge != NULL) {
				janus_mutex_lock(&audiobridge->mutex);
				event = json_object();
				json_object_set_new(event, "audiobridge", json_string("event"));
				json_object_set_new(event, "room", json_integer(audiobridge->room_id));
				json_object_set_new(event, "leaving", json_integer(participant->user_id));
				GHashTableIter iter;
				gpointer value;
				g_hash_table_iter_init(&iter, audiobridge->participants);
				while (g_hash_table_iter_next(&iter, NULL, &value)) {
					janus_audiobridge_participant *p = value;
					if(p == participant) {
						continue;	/* Skip the new participant itself */
					}
					JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
					int ret = gateway->push_event(p->session->handle, &janus_audiobridge_plugin, NULL, event, NULL);
					JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
				}
				json_decref(event);
				/* Actually leave the room... */
				g_hash_table_remove(audiobridge->participants, &participant->user_id);
				participant->room = NULL;
			}
			/* Get rid of queued packets */
			janus_mutex_lock(&participant->qmutex);
			participant->active = FALSE;
			participant->prebuffering = TRUE;
			while(participant->inbuf) {
				GList *first = g_list_first(participant->inbuf);
				janus_audiobridge_rtp_relay_packet *pkt = (janus_audiobridge_rtp_relay_packet *)first->data;
				participant->inbuf = g_list_remove_link(participant->inbuf, first);
				first = NULL;
				if(pkt == NULL)
					continue;
				if(pkt->data)
					g_free(pkt->data);
				pkt->data = NULL;
				g_free(pkt);
				pkt = NULL;
			}
			janus_mutex_unlock(&participant->qmutex);
			/* Stop recording, if we were */
			janus_mutex_lock(&participant->rec_mutex);
			if(participant->arc) {
				janus_recorder_close(participant->arc);
				JANUS_LOG(LOG_INFO, "Closed user's audio recording %s\n", participant->arc->filename ? participant->arc->filename : "??");
				janus_recorder_free(participant->arc);
			}
			participant->arc = NULL;
			janus_mutex_unlock(&participant->rec_mutex);
			/* Done */
			if(audiobridge != NULL)
				janus_mutex_unlock(&audiobridge->mutex);
			janus_mutex_unlock(&rooms_mutex);
		} else {
			JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
			error_code = JANUS_AUDIOBRIDGE_ERROR_INVALID_REQUEST;
			g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
			goto error;
		}

		/* Prepare JSON event */
		JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
		/* Any SDP to handle? */
		const char *msg_sdp_type = json_string_value(json_object_get(msg->jsep, "type"));
		const char *msg_sdp = json_string_value(json_object_get(msg->jsep, "sdp"));
		if(!msg_sdp) {
			int ret = gateway->push_event(msg->handle, &janus_audiobridge_plugin, msg->transaction, event, NULL);
			JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
			json_decref(event);
		} else {
			JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg_sdp_type, msg_sdp);
			const char *type = NULL;
			if(!strcasecmp(msg_sdp_type, "offer"))
				type = "answer";
			if(!strcasecmp(msg_sdp_type, "answer"))
				type = "offer";
			/* Fill the SDP template and use that as our answer */
			janus_audiobridge_participant *participant = (janus_audiobridge_participant *)session->participant;
			char sdp[1024];
			/* What is the Opus payload type? */
			participant->opus_pt = janus_get_codec_pt(msg_sdp, "opus");
			JANUS_LOG(LOG_VERB, "Opus payload type is %d\n", participant->opus_pt);
			g_snprintf(sdp, 1024, sdp_template,
				janus_get_real_time(),			/* We need current time here */
				janus_get_real_time(),			/* We need current time here */
				participant->room->room_name,	/* Audio bridge name */
				participant->opus_pt,			/* Opus payload type */
				participant->opus_pt,			/* Opus payload type */
				participant->opus_pt, 			/* Opus payload type and room sampling rate */
				participant->room->sampling_rate);
			/* Did the peer negotiate video? */
			if(strstr(msg_sdp, "m=video") != NULL) {
				/* If so, reject it */
				g_strlcat(sdp, "m=video 0 RTP/SAVPF 0\r\n", 1024);				
			}
			/* Did the peer negotiate data channels? */
			if(strstr(msg_sdp, "DTLS/SCTP") != NULL) {
				/* If so, reject them */
				g_strlcat(sdp, "m=application 0 DTLS/SCTP 0\r\n", 1024);
			}
			json_t *jsep = json_pack("{ssss}", "type", type, "sdp", sdp);
			/* How long will the gateway take to push the event? */
			g_atomic_int_set(&session->hangingup, 0);
			gint64 start = janus_get_monotonic_time();
			int res = gateway->push_event(msg->handle, &janus_audiobridge_plugin, msg->transaction, event, jsep);
			JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
			json_decref(event);
			json_decref(jsep);
			if(res != JANUS_OK) {
				/* TODO Failed to negotiate? We should remove this participant */
			} else {
				/* Notify all other participants that there's a new boy in town */
				janus_mutex_lock(&rooms_mutex);
				janus_audiobridge_room *audiobridge = participant->room;
				janus_mutex_lock(&audiobridge->mutex);
				json_t *list = json_array();
				json_t *pl = json_object();
				json_object_set_new(pl, "id", json_integer(participant->user_id));
				if(participant->display)
					json_object_set_new(pl, "display", json_string(participant->display));
				json_object_set_new(pl, "muted", participant->muted ? json_true() : json_false());
				json_array_append_new(list, pl);
				json_t *pub = json_object();
				json_object_set_new(pub, "audiobridge", json_string("event"));
				json_object_set_new(pub, "room", json_integer(participant->room->room_id));
				json_object_set_new(pub, "participants", list);
				GHashTableIter iter;
				gpointer value;
				g_hash_table_iter_init(&iter, audiobridge->participants);
				while (g_hash_table_iter_next(&iter, NULL, &value)) {
					janus_audiobridge_participant *p = value;
					if(p == participant) {
						continue;	/* Skip the new participant itself */
					}
					JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
					int ret = gateway->push_event(p->session->handle, &janus_audiobridge_plugin, NULL, pub, NULL);
					JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
				}
				json_decref(pub);
				participant->active = TRUE;
				janus_mutex_unlock(&audiobridge->mutex);
				janus_mutex_unlock(&rooms_mutex);
			}
		}
		if(msg)
			janus_audiobridge_message_free(msg);
		msg = NULL;

		continue;
		
error:
		{
			/* Prepare JSON error event */
			json_t *event = json_object();
			json_object_set_new(event, "audiobridge", json_string("event"));
			json_object_set_new(event, "error_code", json_integer(error_code));
			json_object_set_new(event, "error", json_string(error_cause));
			int ret = gateway->push_event(msg->handle, &janus_audiobridge_plugin, msg->transaction, event, NULL);
			JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
			json_decref(event);
			janus_audiobridge_message_free(msg);
		}
	}
	JANUS_LOG(LOG_VERB, "Leaving AudioBridge handler thread\n");
	return NULL;
}

/* Thread to mix the contributions from all participants */
static void *janus_audiobridge_mixer_thread(void *data) {
	JANUS_LOG(LOG_VERB, "Audio bridge thread starting...\n");
	janus_audiobridge_room *audiobridge = (janus_audiobridge_room *)data;
	if(!audiobridge) {
		JANUS_LOG(LOG_ERR, "Invalid room!\n");
		return NULL;
	}
	JANUS_LOG(LOG_VERB, "Thread is for mixing room %"SCNu64" (%s) at rate %"SCNu32"...\n", audiobridge->room_id, audiobridge->room_name, audiobridge->sampling_rate);

	/* Do we need to record the mix? */
	if(audiobridge->record) {
		char filename[255];
		if(audiobridge->record_file)
			g_snprintf(filename, 255, "%s", audiobridge->record_file);
		else
			g_snprintf(filename, 255, "/tmp/janus-audioroom-%"SCNu64".wav", audiobridge->room_id);
		audiobridge->recording = fopen(filename, "wb");
		if(audiobridge->recording == NULL) {
			JANUS_LOG(LOG_WARN, "Recording requested, but could NOT open file %s for writing...\n", filename);
		} else {
			JANUS_LOG(LOG_VERB, "Recording requested, opened file %s for writing\n", filename);
			/* Write WAV header */
			wav_header header = {
				{'R', 'I', 'F', 'F'},
				0,
				{'W', 'A', 'V', 'E'},
				{'f', 'm', 't', ' '},
				16,
				1,
				1,
				audiobridge->sampling_rate,
				audiobridge->sampling_rate * 2,
				2,
				16,
				{'d', 'a', 't', 'a'},
				0
			};
			if(fwrite(&header, 1, sizeof(header), audiobridge->recording) != sizeof(header)) {
				JANUS_LOG(LOG_ERR, "Error writing WAV header...\n");
			}
			fflush(audiobridge->recording);
			audiobridge->record_lastupdate = janus_get_monotonic_time();
		}
	}

	/* Buffer (we allocate assuming 48kHz, although we'll likely use less than that) */
	int samples = audiobridge->sampling_rate/50;
	opus_int32 buffer[960], sumBuffer[960];
	opus_int16 outBuffer[960], *curBuffer = NULL;
	memset(buffer, 0, 960*4);
	memset(sumBuffer, 0, 960*4);
	memset(outBuffer, 0, 960*2);

	/* Timer */
	struct timeval now, before;
	gettimeofday(&before, NULL);
	now.tv_sec = before.tv_sec;
	now.tv_usec = before.tv_usec;
	time_t passed, d_s, d_us;

	/* RTP */
	gint16 seq = 0;
	gint32 ts = 0;

	/* Loop */
	int i=0;
	int count = 0, prev_count = 0;
	while(!g_atomic_int_get(&stopping) && audiobridge->destroyed == 0) {	/* FIXME We need a per-room watchdog as well */
		/* See if it's time to prepare a frame */
		gettimeofday(&now, NULL);
		d_s = now.tv_sec - before.tv_sec;
		d_us = now.tv_usec - before.tv_usec;
		if(d_us < 0) {
			d_us += 1000000;
			--d_s;
		}
		passed = d_s*1000000 + d_us;
		if(passed < 15000) {	/* Let's wait about 15ms at max */
			usleep(1000);
			continue;
		}
		/* Update the reference time */
		before.tv_usec += 20000;
		if(before.tv_usec > 1000000) {
			before.tv_sec++;
			before.tv_usec -= 1000000;
		}
		/* Do we need to mix at all? */
		janus_mutex_lock_nodebug(&audiobridge->mutex);
		count = g_hash_table_size(audiobridge->participants);
		janus_mutex_unlock_nodebug(&audiobridge->mutex);
		if(count == 0) {
			/* No participant, do nothing */
			if(prev_count > 0) {
				JANUS_LOG(LOG_VERB, "Last user just left room %"SCNu64", going idle...\n", audiobridge->room_id);
				prev_count = 0;
			}
			continue;
		}
		if(prev_count == 0) {
			JANUS_LOG(LOG_VERB, "First user just joined room %"SCNu64", waking it up...\n", audiobridge->room_id);
		}
		prev_count = count;
		/* Update RTP header information */
		seq++;
		ts += 960;
		/* Mix all contributions */
		janus_mutex_lock_nodebug(&audiobridge->mutex);
		GList *participants_list = g_hash_table_get_values(audiobridge->participants);
		janus_mutex_unlock_nodebug(&audiobridge->mutex);
		for(i=0; i<samples; i++)
			buffer[i] = 0;
		GList *ps = participants_list;
		while(ps) {
			janus_audiobridge_participant *p = (janus_audiobridge_participant *)ps->data;
			janus_mutex_lock(&p->qmutex);
			if(!p->active || p->muted || p->prebuffering || !p->inbuf) {
				janus_mutex_unlock(&p->qmutex);
				ps = ps->next;
				continue;
			}
			GList *peek = g_list_first(p->inbuf);
			janus_audiobridge_rtp_relay_packet *pkt = (janus_audiobridge_rtp_relay_packet *)(peek ? peek->data : NULL);
			if(pkt != NULL) {
				curBuffer = (opus_int16 *)pkt->data;
				for(i=0; i<samples; i++)
					buffer[i] += curBuffer[i];
			}
			janus_mutex_unlock(&p->qmutex);
			ps = ps->next;
		}
		/* Are we recording the mix? (only do it if there's someone in, though...) */
		if(audiobridge->recording != NULL && g_list_length(participants_list) > 0) {
			for(i=0; i<samples; i++) {
				/* FIXME Smoothen/Normalize instead of truncating? */
				outBuffer[i] = buffer[i];
			}
			fwrite(outBuffer, sizeof(opus_int16), samples, audiobridge->recording);
			/* Every 5 seconds we update the wav header */
			gint64 now = janus_get_monotonic_time();
			if(now - audiobridge->record_lastupdate >= 5*G_USEC_PER_SEC) {
				audiobridge->record_lastupdate = now;
				/* Update the length in the header */
				fseek(audiobridge->recording, 0, SEEK_END);
				long int size = ftell(audiobridge->recording);
				if(size >= 8) {
					size -= 8;
					fseek(audiobridge->recording, 4, SEEK_SET);
					fwrite(&size, sizeof(uint32_t), 1, audiobridge->recording);
					size += 8;
					fseek(audiobridge->recording, 40, SEEK_SET);
					fwrite(&size, sizeof(uint32_t), 1, audiobridge->recording);
					fflush(audiobridge->recording);
					fseek(audiobridge->recording, 0, SEEK_END);
				}
			}
		}
		/* Send proper packet to each participant (remove own contribution) */
		ps = participants_list;
		while(ps) {
			janus_audiobridge_participant *p = (janus_audiobridge_participant *)ps->data;
			janus_audiobridge_rtp_relay_packet *pkt = NULL;
			janus_mutex_lock(&p->qmutex);
			if(p->active && !p->muted && !p->prebuffering && p->inbuf) {
				GList *first = g_list_first(p->inbuf);
				pkt = (janus_audiobridge_rtp_relay_packet *)(first ? first->data : NULL);
				p->inbuf = g_list_delete_link(p->inbuf, first);
			}
			janus_mutex_unlock(&p->qmutex);
			curBuffer = (opus_int16 *)(pkt ? pkt->data : NULL);
			for(i=0; i<samples; i++)
				sumBuffer[i] = buffer[i] - (curBuffer ? (curBuffer[i]) : 0);
			for(i=0; i<samples; i++)
				/* FIXME Smoothen/Normalize instead of truncating? */
				outBuffer[i] = sumBuffer[i];
			/* Enqueue this mixed frame for encoding in the participant thread */
			janus_audiobridge_rtp_relay_packet *mixedpkt = g_malloc0(sizeof(janus_audiobridge_rtp_relay_packet));
			if(mixedpkt != NULL) {
				mixedpkt->data = g_malloc0(samples*2);
				memcpy(mixedpkt->data, outBuffer, samples*2);
				mixedpkt->length = samples;	/* We set the number of samples here, not the data length */
				mixedpkt->timestamp = ts;
				mixedpkt->seq_number = seq;
				mixedpkt->ssrc = audiobridge->room_id;
				g_async_queue_push(p->outbuf, mixedpkt);
			}
			if(pkt) {
				if(pkt->data)
					g_free(pkt->data);
				pkt->data = NULL;
				g_free(pkt);
				pkt = NULL;
			}
			ps = ps->next;
		}
		g_list_free(participants_list);
	}
	if(audiobridge->recording) {
		/* Update the length in the header */
		fseek(audiobridge->recording, 0, SEEK_END);
		long int size = ftell(audiobridge->recording);
		if(size >= 8) {
			size -= 8;
			fseek(audiobridge->recording, 4, SEEK_SET);
			fwrite(&size, sizeof(uint32_t), 1, audiobridge->recording);
			size += 8;
			fseek(audiobridge->recording, 40, SEEK_SET);
			fwrite(&size, sizeof(uint32_t), 1, audiobridge->recording);
			fflush(audiobridge->recording);
			fclose(audiobridge->recording);
		}
	}
	JANUS_LOG(LOG_VERB, "Leaving mixer thread for room %"SCNu64" (%s)...\n", audiobridge->room_id, audiobridge->room_name);

	/* We'll let the watchdog worry about free resources */
	old_rooms = g_list_append(old_rooms, audiobridge);

	return NULL;
}

/* Thread to encode a mixed frame and send it to a specific participant */
static void *janus_audiobridge_participant_thread(void *data) {
	JANUS_LOG(LOG_VERB, "AudioBridge Participant thread starting...\n");
	janus_audiobridge_participant *participant = (janus_audiobridge_participant *)data;
	if(!participant) {
		JANUS_LOG(LOG_ERR, "Invalid participant!\n");
		g_thread_unref(g_thread_self());
		return NULL;
	}
	JANUS_LOG(LOG_VERB, "Thread is for participant %"SCNu64" (%s)\n", participant->user_id, participant->display ? participant->display : "??");
	janus_audiobridge_session *session = participant->session;

	/* Output buffer */
	janus_audiobridge_rtp_relay_packet *outpkt = g_malloc0(sizeof(janus_audiobridge_rtp_relay_packet));
	outpkt->data = (rtp_header *)g_malloc0(1500);
	outpkt->ssrc = 0;
	outpkt->timestamp = 0;
	outpkt->seq_number = 0;
	unsigned char *payload = (unsigned char *)outpkt->data;
	memset(payload, 0, 1500);

	janus_audiobridge_rtp_relay_packet *mixedpkt = NULL;

	/* Start working: check the outgoing queue for packets, then encode and send them */
	while(!g_atomic_int_get(&stopping) && session->destroyed == 0) {
		if(!participant->active || !participant->encoder) {
			/* Wait until the participant is in a room */
			g_usleep(10000);
			continue;
		}
		if(g_async_queue_length(participant->outbuf) == 0) {
			/* Nothing to do */
			g_usleep(5000);
			continue;
		}
		mixedpkt = g_async_queue_pop(participant->outbuf);
		if(mixedpkt != NULL && session->destroyed == 0) {
			/* Encode raw frame to Opus */
			if(participant->active && participant->encoder) {
				participant->working = TRUE;
				opus_int16 *outBuffer = (opus_int16 *)mixedpkt->data;
				outpkt->length = opus_encode(participant->encoder, outBuffer, mixedpkt->length, payload+12, BUFFER_SAMPLES-12);
				participant->working = FALSE;
				if(outpkt->length < 0) {
					JANUS_LOG(LOG_ERR, "[Opus] Ops! got an error encoding the Opus frame: %d (%s)\n", outpkt->length, opus_strerror(outpkt->length));
				} else {
					outpkt->length += 12;	/* Take the RTP header into consideration */
					/* Update RTP header */
					outpkt->data->version = 2;
					outpkt->data->markerbit = 0;	/* FIXME Should be 1 for the first packet */
					outpkt->data->seq_number = htons(mixedpkt->seq_number);
					outpkt->data->timestamp = htonl(mixedpkt->timestamp);
					outpkt->data->ssrc = htonl(mixedpkt->ssrc);	/* The gateway will fix this anyway */
					/* Backup the actual timestamp and sequence number set by the audiobridge, in case a room is changed */
					outpkt->ssrc = mixedpkt->ssrc;
					outpkt->timestamp = mixedpkt->timestamp;
					outpkt->seq_number = mixedpkt->seq_number;
					janus_audiobridge_relay_rtp_packet(participant->session, outpkt);
				}
			}
			if(mixedpkt) {
				if(mixedpkt->data)
					g_free(mixedpkt->data);
				mixedpkt->data = NULL;
				g_free(mixedpkt);
				mixedpkt = NULL;
			}
		}
	}
	/* We're done, get rid of the resources */
	if(outpkt != NULL) {
		if(outpkt->data != NULL) {
			g_free(outpkt->data);
			outpkt->data = NULL;
		}
		g_free(outpkt);
		outpkt = NULL;
	}
	/* Empty the outgoing queue if there was something still in */
	while(g_async_queue_length(participant->outbuf) > 0) {
		janus_audiobridge_rtp_relay_packet *pkt = g_async_queue_pop(participant->outbuf);
		if(pkt == NULL)
			continue;
		if(pkt->data)
			g_free(pkt->data);
		pkt->data = NULL;
		g_free(pkt);
		pkt = NULL;
	}
	JANUS_LOG(LOG_VERB, "AudioBridge Participant thread leaving...\n");
	return NULL;
}

static void janus_audiobridge_relay_rtp_packet(gpointer data, gpointer user_data) {
	janus_audiobridge_rtp_relay_packet *packet = (janus_audiobridge_rtp_relay_packet *)user_data;
	if(!packet || !packet->data || packet->length < 1) {
		JANUS_LOG(LOG_ERR, "Invalid packet...\n");
		return;
	}
	janus_audiobridge_session *session = (janus_audiobridge_session *)data;
	if(!session || !session->handle) {
		// JANUS_LOG(LOG_ERR, "Invalid session...\n");
		return;
	}
	if(!session->started) {
		// JANUS_LOG(LOG_ERR, "Streaming not started yet for this session...\n");
		return;
	}
	janus_audiobridge_participant *participant = session->participant;
	/* Set the payload type */
	packet->data->type = participant->opus_pt;
	/* Fix sequence number and timestamp (room switching may be involved) */
	if(ntohl(packet->data->ssrc) != participant->context.a_last_ssrc) {
		participant->context.a_last_ssrc = ntohl(packet->data->ssrc);
		participant->context.a_base_ts_prev = participant->context.a_last_ts;
		participant->context.a_base_ts = packet->timestamp;
		participant->context.a_base_seq_prev = participant->context.a_last_seq;
		participant->context.a_base_seq = packet->seq_number;
	}
	/* Compute a coherent timestamp and sequence number */
	participant->context.a_last_ts = (packet->timestamp-participant->context.a_base_ts)
		+ participant->context.a_base_ts_prev+960;	/* FIXME When switching, we assume Opus and so a 960 ts step */
	participant->context.a_last_seq = (packet->seq_number-participant->context.a_base_seq)+participant->context.a_base_seq_prev+1;
	/* Update the timestamp and sequence number in the RTP packet, and send it */
	packet->data->timestamp = htonl(participant->context.a_last_ts);
	packet->data->seq_number = htons(participant->context.a_last_seq);
	if(gateway != NULL)
		gateway->relay_rtp(session->handle, 0, (char *)packet->data, packet->length);
	/* Restore the timestamp and sequence number to what the publisher set them to */
	packet->data->timestamp = htonl(packet->timestamp);
	packet->data->seq_number = htons(packet->seq_number);
}
